libpldm/base: Add MultipartReceive req decoding

Adds MultipartReceive (0x09) request decoding, per DSP0240 v1.1.0
section 8.6.5.

Signed-off-by: Joe Komlodi <komlodi@google.com>
Change-Id: I8d37a9abb7361cc7a238b5b322907739343ff473
diff --git a/libpldm/base.c b/libpldm/base.c
index 83cd22a..9a5ae09 100644
--- a/libpldm/base.c
+++ b/libpldm/base.c
@@ -398,6 +398,56 @@
 	return PLDM_SUCCESS;
 }
 
+int decode_multipart_receive_req(
+    const struct pldm_msg *msg, size_t payload_length, uint8_t *pldm_type,
+    uint8_t *transfer_opflag, uint32_t *transfer_ctx, uint32_t *transfer_handle,
+    uint32_t *section_offset, uint32_t *section_length)
+{
+	if (msg == NULL || pldm_type == NULL || transfer_opflag == NULL ||
+	    transfer_ctx == NULL || transfer_handle == NULL ||
+	    section_offset == NULL || section_length == NULL) {
+		return PLDM_ERROR_INVALID_DATA;
+	}
+
+	if (payload_length != PLDM_MULTIPART_RECEIVE_REQ_BYTES) {
+		return PLDM_ERROR_INVALID_LENGTH;
+	}
+
+	struct pldm_multipart_receive_req *request =
+	    (struct pldm_multipart_receive_req *)msg->payload;
+
+	if (request->pldm_type != PLDM_BASE) {
+		return PLDM_ERROR_INVALID_PLDM_TYPE;
+	}
+
+	// Any enum value above PLDM_XFER_CURRENT_PART is invalid.
+	if (request->transfer_opflag > PLDM_XFER_CURRENT_PART) {
+		return PLDM_INVALID_TRANSFER_OPERATION_FLAG;
+	}
+
+	// A section offset of 0 is only valid on FIRST_PART or COMPLETE Xfers.
+	uint32_t sec_offset = le32toh(request->section_offset);
+	if (sec_offset == 0 &&
+	    (request->transfer_opflag != PLDM_XFER_FIRST_PART &&
+	     request->transfer_opflag != PLDM_XFER_COMPLETE)) {
+		return PLDM_ERROR_INVALID_DATA;
+	}
+
+	uint32_t handle = le32toh(request->transfer_handle);
+	if (handle == 0 && request->transfer_opflag != PLDM_XFER_COMPLETE) {
+		return PLDM_ERROR_INVALID_DATA;
+	}
+
+	*pldm_type = request->pldm_type;
+	*transfer_opflag = request->transfer_opflag;
+	*transfer_ctx = request->transfer_ctx;
+	*transfer_handle = handle;
+	*section_offset = sec_offset;
+	*section_length = le32toh(request->section_length);
+
+	return PLDM_SUCCESS;
+}
+
 int encode_cc_only_resp(uint8_t instance_id, uint8_t type, uint8_t command,
 			uint8_t cc, struct pldm_msg *msg)
 {
diff --git a/libpldm/base.h b/libpldm/base.h
index 99fd6af..be1e355 100644
--- a/libpldm/base.h
+++ b/libpldm/base.h
@@ -28,7 +28,8 @@
 	PLDM_GET_TID = 0x2,
 	PLDM_GET_PLDM_VERSION = 0x3,
 	PLDM_GET_PLDM_TYPES = 0x4,
-	PLDM_GET_PLDM_COMMANDS = 0x5
+	PLDM_GET_PLDM_COMMANDS = 0x5,
+	PLDM_MULTIPART_RECEIVE = 0x9,
 };
 
 /** @brief PLDM base codes
@@ -49,6 +50,14 @@
 	PLDM_GET_FIRSTPART = 1,
 };
 
+enum transfer_multipart_op_flag {
+	PLDM_XFER_FIRST_PART = 0,
+	PLDM_XFER_NEXT_PART = 1,
+	PLDM_XFER_ABORT = 2,
+	PLDM_XFER_COMPLETE = 3,
+	PLDM_XFER_CURRENT_PART = 4,
+};
+
 enum transfer_resp_flag {
 	PLDM_START = 0x01,
 	PLDM_MIDDLE = 0x02,
@@ -88,6 +97,7 @@
 #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
+#define PLDM_MULTIPART_RECEIVE_REQ_BYTES 18
 
 #define PLDM_VERSION_0 0
 #define PLDM_CURRENT_VERSION PLDM_VERSION_0
@@ -214,6 +224,23 @@
 	uint8_t tid;		 //!< PLDM GetTID TID field
 } __attribute__((packed));
 
+/** @struct pldm_multipart_receive_req
+ *
+ * Structure representing PLDM multipart receive request.
+ */
+struct pldm_multipart_receive_req {
+	uint8_t pldm_type;	  //!< PLDM Type for the MultipartReceive
+				  //!< command.
+	uint8_t transfer_opflag;  //!< PLDM MultipartReceive operation flag.
+	uint32_t transfer_ctx;	  //!< Protocol-specifc context for this
+				  //!< transfer.
+	uint32_t transfer_handle; //!< handle to identify the part of data to be
+				  //!< received.
+	uint32_t section_offset;  //!< The start offset for the requested
+				  //!< section.
+	uint32_t section_length;  //!< The length (in bytes) of the section
+				  //!< requested.
+} __attribute__((packed));
 /**
  * @brief Populate the PLDM message with the PLDM header.The caller of this API
  *        allocates buffer for the PLDM header when forming the PLDM message.
@@ -472,6 +499,27 @@
 int encode_get_tid_resp(uint8_t instance_id, uint8_t completion_code,
 			uint8_t tid, struct pldm_msg *msg);
 
+/* Responder */
+
+/* MultipartRecieve */
+
+/** @brief Decode a PLDM MultipartReceive request message
+ *
+ *  @param[in] msg - Request message
+ *  @param[in] payload_length - length of request message payload
+ *  @param[out] pldm_type - PLDM type for which version is requested
+ *  @param[out] transfer_opflag - Transfer Flag
+ *  @param[out] transfer_ctx - The context of the packet
+ *  @param[out] transfer_handle - The handle of data
+ *  @param[out] section_offset - The start of the requested section
+ *  @param[out] section_length - The length of the requested section
+ *  @return pldm_completion_codes
+ */
+int decode_multipart_receive_req(
+    const struct pldm_msg *msg, size_t payload_length, uint8_t *pldm_type,
+    uint8_t *transfer_opflag, uint32_t *transfer_ctx, uint32_t *transfer_handle,
+    uint32_t *section_offset, uint32_t *section_length);
+
 /** @brief Create a PLDM response message containing only cc
  *
  *  @param[in] instance_id - Message's instance id
diff --git a/libpldm/tests/libpldm_base_test.cpp b/libpldm/tests/libpldm_base_test.cpp
index b8b25d3..b3b1383 100644
--- a/libpldm/tests/libpldm_base_test.cpp
+++ b/libpldm/tests/libpldm_base_test.cpp
@@ -524,6 +524,221 @@
     EXPECT_EQ(tid, 1);
 }
 
+TEST(MultipartReceive, testDecodeRequestPass)
+{
+    constexpr uint8_t kPldmType = PLDM_BASE;
+    constexpr uint8_t kFlag = PLDM_XFER_FIRST_PART;
+    constexpr uint32_t kTransferCtx = 0x01;
+    constexpr uint32_t kTransferHandle = 0x10;
+    constexpr uint32_t kSectionOffset = 0x0;
+    constexpr uint32_t kSectionLength = 0x10;
+    uint8_t pldm_type = 0x0;
+    uint8_t flag = PLDM_GET_FIRSTPART;
+    uint32_t transfer_ctx;
+    uint32_t transfer_handle;
+    uint32_t section_offset;
+    uint32_t section_length;
+
+    // Header values don't matter for this test.
+    pldm_msg_hdr hdr{};
+    // Assign values to the packet struct and memcpy to ensure correct byte
+    // ordering.
+    pldm_multipart_receive_req req_pkt = {
+        .pldm_type = kPldmType,
+        .transfer_opflag = kFlag,
+        .transfer_ctx = kTransferCtx,
+        .transfer_handle = kTransferHandle,
+        .section_offset = kSectionOffset,
+        .section_length = kSectionLength,
+    };
+    std::vector<uint8_t> req(sizeof(hdr) + PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    std::memcpy(req.data(), &hdr, sizeof(hdr));
+    std::memcpy(req.data() + sizeof(hdr), &req_pkt, sizeof(req_pkt));
+
+    pldm_msg* pldm_request = reinterpret_cast<pldm_msg*>(req.data());
+    int rc = decode_multipart_receive_req(
+        pldm_request, req.size() - hdrSize, &pldm_type, &flag, &transfer_ctx,
+        &transfer_handle, &section_offset, &section_length);
+
+    EXPECT_EQ(rc, PLDM_SUCCESS);
+    EXPECT_EQ(pldm_type, kPldmType);
+    EXPECT_EQ(flag, kFlag);
+    EXPECT_EQ(transfer_ctx, kTransferCtx);
+    EXPECT_EQ(transfer_handle, kTransferHandle);
+    EXPECT_EQ(section_offset, kSectionOffset);
+    EXPECT_EQ(section_length, kSectionLength);
+}
+
+TEST(MultipartReceive, testDecodeRequestFailNullData)
+{
+    EXPECT_EQ(decode_multipart_receive_req(NULL, 0, NULL, NULL, NULL, NULL,
+                                           NULL, NULL),
+              PLDM_ERROR_INVALID_DATA);
+}
+
+TEST(MultipartReceive, testDecodeRequestFailBadLength)
+{
+    constexpr uint8_t kPldmType = PLDM_BASE;
+    constexpr uint8_t kFlag = PLDM_XFER_FIRST_PART;
+    uint8_t pldm_type;
+    uint8_t flag;
+    uint32_t transfer_ctx;
+    uint32_t transfer_handle;
+    uint32_t section_offset;
+    uint32_t section_length;
+
+    // Header values don't matter for this test.
+    pldm_msg_hdr hdr{};
+    // Assign values to the packet struct and memcpy to ensure correct byte
+    // ordering.
+    pldm_multipart_receive_req req_pkt{};
+    req_pkt.pldm_type = kPldmType;
+    req_pkt.transfer_opflag = kFlag;
+
+    std::vector<uint8_t> req(sizeof(hdr) + PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    std::memcpy(req.data(), &hdr, sizeof(hdr));
+    std::memcpy(req.data() + sizeof(hdr), &req_pkt, sizeof(req_pkt));
+
+    pldm_msg* pldm_request = reinterpret_cast<pldm_msg*>(req.data());
+    EXPECT_EQ(decode_multipart_receive_req(
+                  pldm_request, (req.size() - hdrSize) + 1, &pldm_type, &flag,
+                  &transfer_ctx, &transfer_handle, &section_offset,
+                  &section_length),
+              PLDM_ERROR_INVALID_LENGTH);
+}
+
+TEST(MultipartReceive, testDecodeRequestFailBadPldmType)
+{
+    constexpr uint8_t kPldmType = 0xff;
+    constexpr uint8_t kFlag = PLDM_XFER_FIRST_PART;
+    uint8_t pldm_type;
+    uint8_t flag;
+    uint32_t transfer_ctx;
+    uint32_t transfer_handle;
+    uint32_t section_offset;
+    uint32_t section_length;
+
+    // Header values don't matter for this test.
+    pldm_msg_hdr hdr{};
+    // Assign values to the packet struct and memcpy to ensure correct byte
+    // ordering.
+    pldm_multipart_receive_req req_pkt{};
+    req_pkt.pldm_type = kPldmType;
+    req_pkt.transfer_opflag = kFlag;
+
+    std::vector<uint8_t> req(sizeof(hdr) + PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    std::memcpy(req.data(), &hdr, sizeof(hdr));
+    std::memcpy(req.data() + sizeof(hdr), &req_pkt, sizeof(req_pkt));
+
+    pldm_msg* pldm_request = reinterpret_cast<pldm_msg*>(req.data());
+    EXPECT_EQ(decode_multipart_receive_req(pldm_request, req.size() - hdrSize,
+                                           &pldm_type, &flag, &transfer_ctx,
+                                           &transfer_handle, &section_offset,
+                                           &section_length),
+              PLDM_ERROR_INVALID_PLDM_TYPE);
+}
+
+TEST(MultipartReceive, testDecodeRequestFailBadTransferFlag)
+{
+    constexpr uint8_t kPldmType = PLDM_BASE;
+    constexpr uint8_t kFlag = PLDM_XFER_CURRENT_PART + 0x10;
+    uint8_t pldm_type;
+    uint8_t flag;
+    uint32_t transfer_ctx;
+    uint32_t transfer_handle;
+    uint32_t section_offset;
+    uint32_t section_length;
+
+    // Header values don't matter for this test.
+    pldm_msg_hdr hdr{};
+    // Assign values to the packet struct and memcpy to ensure correct byte
+    // ordering.
+    pldm_multipart_receive_req req_pkt{};
+    req_pkt.pldm_type = kPldmType;
+    req_pkt.transfer_opflag = kFlag;
+
+    std::vector<uint8_t> req(sizeof(hdr) + PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    std::memcpy(req.data(), &hdr, sizeof(hdr));
+    std::memcpy(req.data() + sizeof(hdr), &req_pkt, sizeof(req_pkt));
+
+    pldm_msg* pldm_request = reinterpret_cast<pldm_msg*>(req.data());
+    EXPECT_EQ(decode_multipart_receive_req(pldm_request, req.size() - hdrSize,
+                                           &pldm_type, &flag, &transfer_ctx,
+                                           &transfer_handle, &section_offset,
+                                           &section_length),
+              PLDM_INVALID_TRANSFER_OPERATION_FLAG);
+}
+
+TEST(MultipartReceive, testDecodeRequestFailBadOffset)
+{
+    constexpr uint8_t kPldmType = PLDM_BASE;
+    constexpr uint8_t kFlag = PLDM_XFER_NEXT_PART;
+    constexpr uint32_t kTransferHandle = 0x01;
+    constexpr uint32_t kSectionOffset = 0x0;
+    uint8_t pldm_type;
+    uint8_t flag;
+    uint32_t transfer_ctx;
+    uint32_t transfer_handle;
+    uint32_t section_offset;
+    uint32_t section_length;
+
+    // Header values don't matter for this test.
+    pldm_msg_hdr hdr{};
+    // Assign values to the packet struct and memcpy to ensure correct byte
+    // ordering.
+    pldm_multipart_receive_req req_pkt{};
+    req_pkt.pldm_type = kPldmType;
+    req_pkt.transfer_opflag = kFlag;
+    req_pkt.transfer_handle = kTransferHandle;
+    req_pkt.section_offset = kSectionOffset;
+
+    std::vector<uint8_t> req(sizeof(hdr) + PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    std::memcpy(req.data(), &hdr, sizeof(hdr));
+    std::memcpy(req.data() + sizeof(hdr), &req_pkt, sizeof(req_pkt));
+
+    pldm_msg* pldm_request = reinterpret_cast<pldm_msg*>(req.data());
+    EXPECT_EQ(decode_multipart_receive_req(pldm_request, req.size() - hdrSize,
+                                           &pldm_type, &flag, &transfer_ctx,
+                                           &transfer_handle, &section_offset,
+                                           &section_length),
+              PLDM_ERROR_INVALID_DATA);
+}
+
+TEST(MultipartReceive, testDecodeRequestFailBadHandle)
+{
+    constexpr uint8_t kPldmType = PLDM_BASE;
+    constexpr uint8_t kFlag = PLDM_XFER_NEXT_PART;
+    constexpr uint32_t kSectionOffset = 0x100;
+    constexpr uint32_t kTransferHandle = 0x0;
+    uint8_t pldm_type;
+    uint8_t flag;
+    uint32_t transfer_ctx;
+    uint32_t transfer_handle;
+    uint32_t section_offset;
+    uint32_t section_length;
+
+    // Header values don't matter for this test.
+    pldm_msg_hdr hdr{};
+    // Assign values to the packet struct and memcpy to ensure correct byte
+    // ordering.
+    pldm_multipart_receive_req req_pkt{};
+    req_pkt.pldm_type = kPldmType;
+    req_pkt.transfer_opflag = kFlag;
+    req_pkt.transfer_handle = kTransferHandle;
+    req_pkt.section_offset = kSectionOffset;
+
+    std::vector<uint8_t> req(sizeof(hdr) + PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    std::memcpy(req.data(), &hdr, sizeof(hdr));
+    std::memcpy(req.data() + sizeof(hdr), &req_pkt, sizeof(req_pkt));
+
+    pldm_msg* pldm_request = reinterpret_cast<pldm_msg*>(req.data());
+    EXPECT_EQ(decode_multipart_receive_req(pldm_request, req.size() - hdrSize,
+                                           &pldm_type, &flag, &transfer_ctx,
+                                           &transfer_handle, &section_offset,
+                                           &section_length),
+              PLDM_ERROR_INVALID_DATA);
+}
+
 TEST(CcOnlyResponse, testEncode)
 {
     struct pldm_msg responseMsg;