pdr: Remove contained entity from PDR repo

API to remove a contained entity from an entity association PDR. This
API saves record handle of the PLDM PDR record where the contained
entity is found. The record handle we get from this API will be
required to update PDR repo by removing the entry corresponding to
this entity in the PDR table. Also the Node associated with this pldm
entity is deleted from PDR tree. The PLDM PDR record which holds the
contained entity to be removed is found with the parent entity
properties sent to this API as input.

Tested By: Unit tested by removing entities from PDR repo

Change-Id: I55fb898a0e67d8c9cd95594b4ec6a6a4866c9531
Signed-off-by: Varsha Kaverappa <vkaverap@in.ibm.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9bd9b0a..2892e7b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@
 1. oem: meta: Add decode_oem_meta_file_io_write_req()
 2. oem: meta: Add decode_oem_meta_file_io_read_req()
 3. oem: meta: Add encode_oem_meta_file_io_read_resp()
+4. pdr: Add pldm_entity_association_pdr_remove_contained_entity()
 
 ### Deprecated
 
diff --git a/include/libpldm/pdr.h b/include/libpldm/pdr.h
index ff3279c..089f2bb 100644
--- a/include/libpldm/pdr.h
+++ b/include/libpldm/pdr.h
@@ -626,6 +626,24 @@
 					 size_t *num_entities,
 					 pldm_entity **entities);
 
+/** @brief Remove a contained entity from an entity association PDR
+ *
+ *  @param[in] repo - opaque pointer acting as a PDR repo handle
+ *  @param[in] entity - the pldm entity to be deleted. Data inside the entity struct must be
+ *  			host-endianess format
+ *  @param[in] is_remote - indicates which PDR to remove, local or remote
+ *  @param[in-out] pdr_record_handle - record handle of the container entity which has to be removed.
+ *                                     PLDM will use this record handle to updated the PDR repo so
+ *                                     that entry corresponding to this entity is removed from PDR
+ *                                     table.
+ *
+ *  @return 0 on success, -EINVAL if the arguments are invalid, -ENOMEM if an internal memory
+ *  allocation fails, or -EOVERFLOW if given data is too large for memory allocated
+ */
+int pldm_entity_association_pdr_remove_contained_entity(
+	pldm_pdr *repo, pldm_entity *entity, bool is_remote,
+	uint32_t *pdr_record_handle);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/dsp/pdr.c b/src/dsp/pdr.c
index 45f1d86..5610205 100644
--- a/src/dsp/pdr.c
+++ b/src/dsp/pdr.c
@@ -1552,7 +1552,7 @@
 	bool pdr_added = false;
 	uint16_t new_pdr_size;
 	uint16_t container_id = 0;
-	uint8_t *container_id_addr = NULL;
+	void *container_id_addr;
 	struct pldm_msgbuf _dst;
 	struct pldm_msgbuf *dst = &_dst;
 	struct pldm_msgbuf _src_p;
@@ -1622,12 +1622,14 @@
 		goto cleanup_new_record_data;
 	}
 
+	container_id_addr = NULL;
 	// extract pointer for container ID and save the address
 	rc = pldm_msgbuf_span_required(dst, sizeof(container_id),
 				       (void **)&container_id_addr);
 	if (rc) {
 		goto cleanup_new_record_data;
 	}
+	assert(container_id_addr);
 	pldm_msgbuf_insert_uint8(dst, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
 	pldm_msgbuf_copy(dst, src_p, uint16_t, entity_type);
 	pldm_msgbuf_copy(dst, src_p, uint16_t, entity_instance_num);
@@ -1670,3 +1672,208 @@
 	free(new_record);
 	return rc;
 }
+
+LIBPLDM_CC_NONNULL
+static bool pldm_entity_cmp(const struct pldm_entity *l,
+			    const struct pldm_entity *r)
+{
+	return l->entity_type == r->entity_type &&
+	       l->entity_instance_num == r->entity_instance_num &&
+	       l->entity_container_id == r->entity_container_id;
+}
+
+/* Find record handle of a PDR record from PDR repo and
+ * entity
+ */
+LIBPLDM_CC_NONNULL
+static int pldm_entity_association_find_record_handle_by_entity(
+	pldm_pdr *repo, pldm_entity *entity, bool is_remote,
+	uint32_t *record_handle)
+{
+	uint8_t num_children = 0;
+	uint8_t hdr_type = 0;
+	int rc = 0;
+	size_t skip_data_size = 0;
+	pldm_pdr_record *record = repo->first;
+	struct pldm_msgbuf _dst;
+	struct pldm_msgbuf *dst = &_dst;
+
+	while (record != NULL) {
+		rc = pldm_msgbuf_init_errno(dst,
+					    PDR_ENTITY_ASSOCIATION_MIN_SIZE,
+					    record->data, record->size);
+		if (rc) {
+			return rc;
+		}
+		skip_data_size = sizeof(uint32_t) + sizeof(uint8_t);
+		pldm_msgbuf_span_required(dst, skip_data_size, NULL);
+		pldm_msgbuf_extract(dst, hdr_type);
+		if (record->is_remote != is_remote ||
+		    hdr_type != PLDM_PDR_ENTITY_ASSOCIATION) {
+			goto cleanup;
+		}
+		skip_data_size = sizeof(uint16_t) + sizeof(uint16_t) +
+				 sizeof(uint16_t) + sizeof(uint8_t) +
+				 sizeof(struct pldm_entity);
+		pldm_msgbuf_span_required(dst, skip_data_size, NULL);
+		pldm_msgbuf_extract(dst, num_children);
+		for (int i = 0; i < num_children; ++i) {
+			struct pldm_entity e;
+
+			if ((rc = pldm_msgbuf_extract(dst, e.entity_type)) ||
+			    (rc = pldm_msgbuf_extract(dst,
+						      e.entity_instance_num)) ||
+			    (rc = pldm_msgbuf_extract(dst,
+						      e.entity_container_id))) {
+				return rc;
+			}
+
+			if (pldm_entity_cmp(entity, &e)) {
+				*record_handle = record->record_handle;
+				return 0;
+			}
+		}
+	cleanup:
+		rc = pldm_msgbuf_destroy(dst);
+		if (rc) {
+			return rc;
+		}
+		record = record->next;
+	}
+	return 0;
+}
+
+LIBPLDM_ABI_TESTING
+int pldm_entity_association_pdr_remove_contained_entity(
+	pldm_pdr *repo, pldm_entity *entity, bool is_remote,
+	uint32_t *pdr_record_handle)
+{
+	uint16_t header_length = 0;
+	uint8_t num_children = 0;
+	struct pldm_msgbuf _src;
+	struct pldm_msgbuf *src = &_src;
+	struct pldm_msgbuf _dst;
+	struct pldm_msgbuf *dst = &_dst;
+	int rc;
+	pldm_pdr_record *record;
+	pldm_pdr_record *prev;
+
+	if (!repo || !entity || !pdr_record_handle) {
+		return -EINVAL;
+	}
+	record = repo->first;
+	prev = repo->first;
+
+	rc = pldm_entity_association_find_record_handle_by_entity(
+		repo, entity, is_remote, pdr_record_handle);
+	if (rc) {
+		return rc;
+	}
+	pldm_pdr_find_record_by_handle(&record, &prev, *pdr_record_handle);
+	if (!record) {
+		return -EINVAL;
+	}
+	// Initialize msg buffer for record and record->data
+	rc = pldm_msgbuf_init_errno(src, PDR_ENTITY_ASSOCIATION_MIN_SIZE,
+				    record->data, record->size);
+	if (rc) {
+		return rc;
+	}
+	// check if removing an entity from record causes overflow before
+	// allocating memory for new_record.
+	if (record->size < sizeof(pldm_entity)) {
+		return -EOVERFLOW;
+	}
+	pldm_pdr_record *new_record = malloc(sizeof(pldm_pdr_record));
+	if (!new_record) {
+		return -ENOMEM;
+	}
+	new_record->data = malloc(record->size - sizeof(pldm_entity));
+	if (!new_record->data) {
+		rc = -ENOMEM;
+		goto cleanup_new_record;
+	}
+	new_record->record_handle = record->record_handle;
+	new_record->size = record->size - sizeof(struct pldm_entity);
+	new_record->is_remote = record->is_remote;
+
+	// Initialize new PDR record with data from original PDR record.
+	// Start with adding the header of original PDR
+	rc = pldm_msgbuf_init_errno(dst, PDR_ENTITY_ASSOCIATION_MIN_SIZE,
+				    new_record->data, new_record->size);
+	if (rc) {
+		goto cleanup_new_record_data;
+	}
+	pldm_msgbuf_copy(dst, src, uint32_t, hdr_record_handle);
+	pldm_msgbuf_copy(dst, src, uint8_t, hdr_version);
+	pldm_msgbuf_copy(dst, src, uint8_t, hdr_type);
+	pldm_msgbuf_copy(dst, src, uint16_t, hdr_record_change_num);
+	// extract the header length from record and decrement size with
+	// size of pldm_entity before inserting the value into new_record.
+	rc = pldm_msgbuf_extract(src, header_length);
+	if (rc) {
+		goto cleanup_new_record_data;
+	}
+	if (header_length < sizeof(pldm_entity)) {
+		rc = -EOVERFLOW;
+		goto cleanup_new_record_data;
+	}
+	header_length -= sizeof(pldm_entity);
+	pldm_msgbuf_insert(dst, header_length);
+	pldm_msgbuf_copy(dst, src, uint16_t, container_id);
+	pldm_msgbuf_copy(dst, src, uint8_t, association_type);
+	pldm_msgbuf_copy(dst, src, uint16_t, entity_type);
+	pldm_msgbuf_copy(dst, src, uint16_t, entity_instance_num);
+	pldm_msgbuf_copy(dst, src, uint16_t, entity_container_id);
+	// extract value of number of children from record and decrement it
+	// by 1 before insert the value to new record.
+	rc = pldm_msgbuf_extract(src, num_children);
+	if (rc) {
+		goto cleanup_new_record_data;
+	}
+	if (num_children == 1) {
+		prev->next = record->next;
+		free(record->data);
+		free(record);
+		goto cleanup_new_record_data;
+	} else if (num_children < 1) {
+		rc = -EOVERFLOW;
+		goto cleanup_new_record_data;
+	}
+	num_children -= 1;
+	pldm_msgbuf_insert(dst, num_children);
+	//Add all children of original PDR to new PDR
+	for (int i = 0; i < num_children + 1; ++i) {
+		struct pldm_entity e;
+
+		if ((rc = pldm_msgbuf_extract(src, e.entity_type)) ||
+		    (rc = pldm_msgbuf_extract(src, e.entity_instance_num)) ||
+		    (rc = pldm_msgbuf_extract(src, e.entity_container_id))) {
+			goto cleanup_new_record_data;
+		}
+
+		if (pldm_entity_cmp(entity, &e)) {
+			continue;
+		}
+
+		pldm_msgbuf_insert(dst, e.entity_type);
+		pldm_msgbuf_insert(dst, e.entity_instance_num);
+		pldm_msgbuf_insert(dst, e.entity_container_id);
+	}
+
+	if ((rc = pldm_msgbuf_destroy(src)) ||
+	    (rc = pldm_msgbuf_destroy(dst)) ||
+	    (rc = pldm_pdr_replace_record(repo, record, prev, new_record))) {
+		goto cleanup_new_record_data;
+	}
+
+	free(record->data);
+	free(record);
+	return rc;
+
+cleanup_new_record_data:
+	free(new_record->data);
+cleanup_new_record:
+	free(new_record);
+	return rc;
+}
diff --git a/tests/dsp/pdr.cpp b/tests/dsp/pdr.cpp
index 04524e8..e6a43f0 100644
--- a/tests/dsp/pdr.cpp
+++ b/tests/dsp/pdr.cpp
@@ -2006,3 +2006,57 @@
     pldm_entity_association_tree_destroy(tree);
 }
 #endif
+
+#ifdef LIBPLDM_API_TESTING
+TEST(EntityAssociationPDR, testRemoveContainedEntity)
+{
+    struct pldm_entity entities[4] = {
+        {.entity_type = 1, .entity_instance_num = 1, .entity_container_id = 2},
+        {.entity_type = 2, .entity_instance_num = 1, .entity_container_id = 2},
+        {.entity_type = 3, .entity_instance_num = 3, .entity_container_id = 3},
+        {.entity_type = 4, .entity_instance_num = 1, .entity_container_id = 2}};
+    struct pldm_entity* base = entities;
+
+    auto tree = pldm_entity_association_tree_init();
+    auto l1 = pldm_entity_association_tree_add_entity(
+        tree, &entities[0], 0xffff, nullptr, PLDM_ENTITY_ASSOCIAION_LOGICAL,
+        false, true, 0xffff);
+
+    EXPECT_NE(l1, nullptr);
+    auto l2 = pldm_entity_association_tree_add_entity(
+        tree, &entities[1], 0xffff, l1, PLDM_ENTITY_ASSOCIAION_PHYSICAL, false,
+        false, 0xffff);
+    EXPECT_NE(l2, nullptr);
+    auto l3 = pldm_entity_association_tree_add_entity(
+        tree, &entities[2], 0xffff, l1, PLDM_ENTITY_ASSOCIAION_PHYSICAL, false,
+        true, 0xffff);
+    EXPECT_NE(l3, nullptr);
+    auto l4 = pldm_entity_association_tree_add_entity(
+        tree, &entities[3], 0xffff, l1, PLDM_ENTITY_ASSOCIAION_PHYSICAL, false,
+        true, 0xffff);
+    EXPECT_NE(l4, nullptr);
+
+    EXPECT_EQ(pldm_entity_get_num_children(l1, PLDM_ENTITY_ASSOCIAION_PHYSICAL),
+              3);
+
+    auto repo = pldm_pdr_init();
+
+    EXPECT_EQ(pldm_entity_association_pdr_add_from_node_with_record_handle(
+                  l1, repo, &base, 4, false, 1, 3),
+              0);
+
+    EXPECT_EQ(pldm_pdr_get_record_count(repo), 1u);
+
+    uint32_t removed_record_handle{};
+    struct pldm_entity entity[1] = {
+        {.entity_type = 2, .entity_instance_num = 1, .entity_container_id = 2}};
+
+    EXPECT_EQ(pldm_entity_association_pdr_remove_contained_entity(
+                  repo, entity, false, &removed_record_handle),
+              0);
+    EXPECT_EQ(removed_record_handle, 3);
+
+    pldm_pdr_destroy(repo);
+    pldm_entity_association_tree_destroy(tree);
+}
+#endif