pdr: Add contained entity to an association PDR

Include function to add a contained entity into an entity association
PDR. A contained entity may be added to an existing container thereby
Imodifying the association PDR or it can be added to the association
PDR by attaching both the container and its contained entity. This API
covers both these cases.

DSP0248_1.2.0 - SECTION 11 explains entity association PDR

Tested By: Adding/Removing an entity(FRU) to an existing PDR on
SIMICS.

Change-Id: Id4ac34f03311dbc7633e7fd6db7f772cfd811a2d
Signed-off-by: Varsha Kaverappa <vkaverap@in.ibm.com>
diff --git a/include/libpldm/pdr.h b/include/libpldm/pdr.h
index ca03c69..97404ff 100644
--- a/include/libpldm/pdr.h
+++ b/include/libpldm/pdr.h
@@ -452,6 +452,42 @@
 					  pldm_pdr *repo, bool is_remote,
 					  uint16_t terminus_handle);
 
+/** @brief Add a contained entity as a remote PDR to an existing entity association PDR.
+ *
+ *  Remote PDRs are PDRs added as a child to an entity in the entity association tree and
+ *  not to the tree directly. This means remote PDRs have a parent PDR in the entity
+ *  association tree to which they are linked.
+ *
+ *  @param[in] repo - opaque pointer to pldm PDR repo
+ *  @param[in] entity - the contained entity to be added
+ *  @param[in] pdr_record_handle - record handle of the container entity to which the remote
+ *  PDR is to be added as a child
+ *
+ *  @return 0 on success, -EINVAL if the arguments are invalid, -ENOMEM if an internal memory
+ *  allocation fails, or -EOVERFLOW if a record handle could not be allocated
+ */
+int pldm_entity_association_pdr_add_contained_entity_to_remote_pdr(
+	pldm_pdr *repo, pldm_entity *entity, uint32_t pdr_record_handle);
+
+/** @brief Creates a new entity association PDR with contained entity & its parent.
+ *
+ *  @param[in] repo - opaque pointer to pldm PDR repo
+ *  @param[in] pdr_record_handle - record handle of the PDR after which the new container
+ *  entity has to be added
+ *  @param[in] parent - the container entity
+ *  @param[in] entity - the contained entity to be added
+ *  @param[in-out] entity_record_handle - record handle of a container entity added to the
+ *  entity association PDR
+ *
+ *  @return 0 on success, -EINVAL if the arguments are invalid, -ENOMEM if an internal memory
+ *  allocation fails, or -EOVERFLOW if a record handle could not be allocated
+ */
+int pldm_entity_association_pdr_create_new(pldm_pdr *repo,
+					   uint32_t pdr_record_handle,
+					   pldm_entity *parent,
+					   pldm_entity *entity,
+					   uint32_t *entity_record_handle);
+
 /** @brief Add entity association pdr from node, or return an error
  *
  *  @param[in] node - opaque pointer acting as a handle to an entity node
diff --git a/src/pdr.c b/src/pdr.c
index 5109d6a..09e87be 100644
--- a/src/pdr.c
+++ b/src/pdr.c
@@ -1,4 +1,5 @@
 /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+#include "msgbuf.h"
 #include <libpldm/pdr.h>
 #include <libpldm/platform.h>
 
@@ -8,6 +9,10 @@
 #include <string.h>
 #include <errno.h>
 
+#define PDR_ENTITY_ASSOCIATION_MIN_SIZE                                        \
+	(sizeof(struct pldm_pdr_hdr) +                                         \
+	 sizeof(struct pldm_pdr_entity_association))
+
 typedef struct pldm_pdr_record {
 	uint32_t record_handle;
 	uint32_t size;
@@ -1250,13 +1255,9 @@
 	if (!pdr || !num_entities || !entities) {
 		return;
 	}
-#define PDR_MIN_SIZE                                                           \
-	(sizeof(struct pldm_pdr_hdr) +                                         \
-	 sizeof(struct pldm_pdr_entity_association))
-	if (pdr_len < PDR_MIN_SIZE) {
+	if (pdr_len < PDR_ENTITY_ASSOCIATION_MIN_SIZE) {
 		return;
 	}
-#undef PDR_MIN_SIZE
 
 	struct pldm_pdr_hdr *hdr = (struct pldm_pdr_hdr *)pdr;
 	if (hdr->type != PLDM_PDR_ENTITY_ASSOCIATION) {
@@ -1300,3 +1301,362 @@
 	*num_entities = l_num_entities;
 	*entities = l_entities;
 }
+
+/* Find the postion of record in pldm_pdr repo and place new_record in
+ * the same position.
+ */
+static int pldm_pdr_replace_record(pldm_pdr *repo, pldm_pdr_record *record,
+				   pldm_pdr_record *prev,
+				   pldm_pdr_record *new_record)
+{
+	assert(repo);
+	assert(record);
+	assert(prev);
+	assert(new_record);
+
+	if (repo->size < record->size) {
+		return -EOVERFLOW;
+	}
+
+	if (repo->size + new_record->size < new_record->size) {
+		return -EOVERFLOW;
+	}
+
+	if (repo->first == record) {
+		repo->first = new_record;
+	} else {
+		prev->next = new_record;
+	}
+	new_record->next = record->next;
+
+	if (repo->last == record) {
+		repo->last = new_record;
+	}
+
+	repo->size = (repo->size - record->size) + new_record->size;
+	return 0;
+}
+
+/* Insert a new record to pldm_pdr repo to a position that comes after
+ * pldm_pdr_record record.
+ */
+static int pldm_pdr_insert_record(pldm_pdr *repo, pldm_pdr_record *record,
+				  pldm_pdr_record *new_record)
+{
+	assert(repo);
+	assert(record);
+	assert(new_record);
+
+	if (repo->size + new_record->size < new_record->size) {
+		return -EOVERFLOW;
+	}
+
+	if (repo->record_count == UINT32_MAX) {
+		return -EOVERFLOW;
+	}
+
+	new_record->next = record->next;
+	record->next = new_record;
+
+	if (repo->last == record) {
+		repo->last = new_record;
+	}
+
+	repo->size = repo->size + new_record->size;
+	++repo->record_count;
+	return 0;
+}
+
+/* Find the position of PDR when its record handle is known
+ */
+static bool pldm_pdr_find_record_by_handle(pldm_pdr_record **record,
+					   pldm_pdr_record **prev,
+					   uint32_t record_handle)
+{
+	assert(record);
+	assert(prev);
+
+	while (*record != NULL) {
+		if ((*record)->record_handle == record_handle) {
+			return true;
+		}
+		*prev = *record;
+		*record = (*record)->next;
+	}
+	return false;
+}
+
+LIBPLDM_ABI_TESTING
+int pldm_entity_association_pdr_add_contained_entity_to_remote_pdr(
+	pldm_pdr *repo, pldm_entity *entity, uint32_t pdr_record_handle)
+{
+	if (!repo || !entity) {
+		return -EINVAL;
+	}
+
+	pldm_pdr_record *record = repo->first;
+	pldm_pdr_record *prev = repo->first;
+	int rc = 0;
+	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;
+
+	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 adding another entity to record causes overflow before
+	// allocating memory for new_record.
+	if (record->size + sizeof(pldm_entity) < 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 increment 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;
+	}
+	static_assert(UINT16_MAX < (SIZE_MAX - sizeof(pldm_entity)),
+		      "Fix the following bounds check.");
+	if (header_length + sizeof(pldm_entity) > UINT16_MAX) {
+		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 increment 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 == UINT8_MAX) {
+		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++) {
+		pldm_msgbuf_copy(dst, src, uint16_t, child_entity_type);
+		pldm_msgbuf_copy(dst, src, uint16_t, child_entity_instance_num);
+		pldm_msgbuf_copy(dst, src, uint16_t, child_entity_container_id);
+	}
+
+	// Add new contained entity as a child of new PDR
+	rc = pldm_msgbuf_destroy(src);
+	if (rc) {
+		goto cleanup_new_record_data;
+	}
+	rc = pldm_msgbuf_init_errno(src, sizeof(struct pldm_entity), entity,
+				    sizeof(struct pldm_entity));
+	if (rc) {
+		goto cleanup_new_record_data;
+	}
+	pldm_msgbuf_copy(dst, src, uint16_t, child_entity_type);
+	pldm_msgbuf_copy(dst, src, uint16_t, child_entity_instance_num);
+	pldm_msgbuf_copy(dst, src, uint16_t, child_entity_container_id);
+
+	rc = pldm_msgbuf_destroy(src);
+	if (rc) {
+		goto cleanup_new_record_data;
+	}
+	rc = pldm_msgbuf_destroy(dst);
+	if (rc) {
+		goto cleanup_new_record_data;
+	}
+
+	rc = pldm_pdr_replace_record(repo, record, prev, new_record);
+	if (rc) {
+		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;
+}
+
+LIBPLDM_ABI_TESTING
+int pldm_entity_association_pdr_create_new(pldm_pdr *repo,
+					   uint32_t pdr_record_handle,
+					   pldm_entity *parent,
+					   pldm_entity *entity,
+					   uint32_t *entity_record_handle)
+{
+	if (!repo || !parent || !entity || !entity_record_handle) {
+		return -EINVAL;
+	}
+
+	if (pdr_record_handle == UINT32_MAX) {
+		return -EOVERFLOW;
+	}
+
+	bool pdr_added = false;
+	uint16_t new_pdr_size;
+	uint16_t container_id = 0;
+	uint8_t *container_id_addr = NULL;
+	struct pldm_msgbuf _dst;
+	struct pldm_msgbuf *dst = &_dst;
+	struct pldm_msgbuf _src_p;
+	struct pldm_msgbuf *src_p = &_src_p;
+	struct pldm_msgbuf _src_c;
+	struct pldm_msgbuf *src_c = &_src_c;
+	int rc = 0;
+
+	pldm_pdr_record *prev = repo->first;
+	pldm_pdr_record *record = repo->first;
+	pdr_added = pldm_pdr_find_record_by_handle(&record, &prev,
+						   pdr_record_handle);
+	if (!pdr_added) {
+		return -ENOENT;
+	}
+
+	static_assert(PDR_ENTITY_ASSOCIATION_MIN_SIZE < UINT16_MAX,
+		      "Truncation ahead");
+	new_pdr_size = PDR_ENTITY_ASSOCIATION_MIN_SIZE;
+	pldm_pdr_record *new_record = malloc(sizeof(pldm_pdr_record));
+	if (!new_record) {
+		return -ENOMEM;
+	}
+
+	new_record->data = malloc(new_pdr_size);
+	if (!new_record->data) {
+		rc = -ENOMEM;
+		goto cleanup_new_record;
+	}
+
+	// Initialise new PDR to be added with the header, size and handle.
+	// Set the position of new PDR
+	*entity_record_handle = pdr_record_handle + 1;
+	new_record->record_handle = *entity_record_handle;
+	new_record->size = new_pdr_size;
+	new_record->is_remote = false;
+
+	rc = pldm_msgbuf_init_errno(dst, PDR_ENTITY_ASSOCIATION_MIN_SIZE,
+				    new_record->data, new_record->size);
+	if (rc) {
+		goto cleanup_new_record_data;
+	}
+
+	// header record handle
+	pldm_msgbuf_insert(dst, *entity_record_handle);
+	// header version
+	pldm_msgbuf_insert_uint8(dst, 1);
+	// header type
+	pldm_msgbuf_insert_uint8(dst, PLDM_PDR_ENTITY_ASSOCIATION);
+	// header change number
+	pldm_msgbuf_insert_uint16(dst, 0);
+	// header length
+	pldm_msgbuf_insert_uint16(dst,
+				  (new_pdr_size - sizeof(struct pldm_pdr_hdr)));
+
+	// Data for new PDR is obtained from parent PDR and new contained entity
+	// is added as the child
+	rc = pldm_msgbuf_init_errno(src_p, sizeof(struct pldm_entity), parent,
+				    sizeof(*parent));
+	if (rc) {
+		goto cleanup_new_record_data;
+	}
+
+	rc = pldm_msgbuf_init_errno(src_c, sizeof(struct pldm_entity), entity,
+				    sizeof(*entity));
+	if (rc) {
+		goto cleanup_new_record_data;
+	}
+
+	// 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;
+	}
+	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);
+	pldm_msgbuf_copy(dst, src_p, uint16_t, entity_container_id);
+	// number of children
+	pldm_msgbuf_insert_uint8(dst, 1);
+
+	// Add new entity as child
+	pldm_msgbuf_copy(dst, src_c, uint16_t, child_entity_type);
+	pldm_msgbuf_copy(dst, src_c, uint16_t, child_entity_instance_num);
+	// Extract and insert child entity container ID and add same value to
+	// container ID of entity
+	pldm_msgbuf_extract(src_c, container_id);
+	pldm_msgbuf_insert(dst, container_id);
+	container_id = htole16(container_id);
+	memcpy(container_id_addr, &container_id, sizeof(uint16_t));
+
+	rc = pldm_msgbuf_destroy(dst);
+	if (rc) {
+		goto cleanup_new_record_data;
+	}
+	rc = pldm_msgbuf_destroy(src_p);
+	if (rc) {
+		goto cleanup_new_record_data;
+	}
+	rc = pldm_msgbuf_destroy(src_c);
+	if (rc) {
+		goto cleanup_new_record_data;
+	}
+
+	rc = pldm_pdr_insert_record(repo, record, new_record);
+	if (rc) {
+		goto cleanup_new_record_data;
+	}
+
+	return rc;
+cleanup_new_record_data:
+	free(new_record->data);
+cleanup_new_record:
+	free(new_record);
+	return rc;
+}
diff --git a/tests/libpldm_pdr_test.cpp b/tests/libpldm_pdr_test.cpp
index 65edf32..0254ebf 100644
--- a/tests/libpldm_pdr_test.cpp
+++ b/tests/libpldm_pdr_test.cpp
@@ -1929,3 +1929,124 @@
     pldm_pdr_destroy(repo);
     pldm_entity_association_tree_destroy(tree);
 }
+
+TEST(EntityAssociationPDR, testAddContainedEntityRemotePDR)
+{
+    // pldm_entity entities[5]{};
+    pldm_entity* entities = (pldm_entity*)malloc(sizeof(pldm_entity) * 5);
+    entities[0].entity_type = 1;
+    entities[1].entity_type = 2;
+    entities[2].entity_type = 3;
+    entities[3].entity_type = 4;
+    entities[4].entity_type = 5;
+
+    auto tree = pldm_entity_association_tree_init();
+    auto l1 = pldm_entity_association_tree_add(
+        tree, &entities[0], 0xffff, nullptr, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+    EXPECT_NE(l1, nullptr);
+    auto l2a = pldm_entity_association_tree_add(
+        tree, &entities[1], 0xffff, l1, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+    EXPECT_NE(l2a, nullptr);
+    auto l2b = pldm_entity_association_tree_add(
+        tree, &entities[2], 0xffff, l1, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+    EXPECT_NE(l2b, nullptr);
+    auto l2c = pldm_entity_association_tree_add(
+        tree, &entities[3], 0xffff, l1, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+    EXPECT_NE(l2c, nullptr);
+    auto l3a = pldm_entity_association_tree_add(
+        tree, &entities[4], 0xffff, l2a, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+    EXPECT_NE(l3a, nullptr);
+
+    auto repo = pldm_pdr_init();
+
+    EXPECT_EQ(pldm_entity_association_pdr_add_from_node_with_record_handle(
+                  l1, repo, &entities, 5, false, 1, 0),
+              0);
+    EXPECT_EQ(pldm_entity_association_pdr_add_from_node_with_record_handle(
+                  l1, repo, &entities, 5, false, 1, 2),
+              0);
+    EXPECT_EQ(pldm_entity_association_pdr_add_from_node_with_record_handle(
+                  l1, repo, &entities, 5, false, 1, 23),
+              0);
+    EXPECT_EQ(pldm_entity_association_pdr_add_from_node_with_record_handle(
+                  l1, repo, &entities, 5, false, 1, 34),
+              0);
+    EXPECT_EQ(pldm_entity_association_pdr_add_from_node_with_record_handle(
+                  l2a, repo, &entities, 5, false, 1, 3),
+              0);
+
+    pldm_entity entity1[1];
+    entity1[0].entity_type = 2;
+
+    EXPECT_EQ(pldm_entity_association_pdr_add_contained_entity_to_remote_pdr(
+                  repo, &entity1[0], 2),
+              0);
+
+    free(entities);
+    pldm_pdr_destroy(repo);
+    pldm_entity_association_tree_destroy(tree);
+}
+
+TEST(EntityAssociationPDR, testAddContainedEntityNew)
+{
+    // pldm_entity entities[5]{};
+    pldm_entity* entities = (pldm_entity*)malloc(sizeof(pldm_entity) * 5);
+    entities[0].entity_type = 1;
+    entities[1].entity_type = 2;
+    entities[2].entity_type = 3;
+    entities[3].entity_type = 4;
+    entities[4].entity_type = 5;
+
+    auto tree = pldm_entity_association_tree_init();
+    auto l1 = pldm_entity_association_tree_add(
+        tree, &entities[0], 0xffff, nullptr, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+    EXPECT_NE(l1, nullptr);
+    auto l2a = pldm_entity_association_tree_add(
+        tree, &entities[1], 0xffff, l1, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+    EXPECT_NE(l2a, nullptr);
+    auto l2b = pldm_entity_association_tree_add(
+        tree, &entities[2], 0xffff, l1, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+    EXPECT_NE(l2b, nullptr);
+    auto l2c = pldm_entity_association_tree_add(
+        tree, &entities[3], 0xffff, l1, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+    EXPECT_NE(l2c, nullptr);
+    auto l3a = pldm_entity_association_tree_add(
+        tree, &entities[4], 0xffff, l2a, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+    EXPECT_NE(l3a, nullptr);
+
+    auto repo = pldm_pdr_init();
+
+    EXPECT_EQ(pldm_entity_association_pdr_add_from_node_with_record_handle(
+                  l1, repo, &entities, 5, false, 1, 0),
+              0);
+    EXPECT_EQ(pldm_entity_association_pdr_add_from_node_with_record_handle(
+                  l1, repo, &entities, 5, false, 1, 2),
+              0);
+    EXPECT_EQ(pldm_entity_association_pdr_add_from_node_with_record_handle(
+                  l1, repo, &entities, 5, false, 1, 23),
+              0);
+    EXPECT_EQ(pldm_entity_association_pdr_add_from_node_with_record_handle(
+                  l1, repo, &entities, 5, false, 1, 34),
+              0);
+    EXPECT_EQ(pldm_entity_association_pdr_add_from_node_with_record_handle(
+                  l2a, repo, &entities, 5, false, 1, 3),
+              0);
+
+    uint32_t updated_record_handle = 0;
+
+    pldm_entity entity2[1]{};
+    entity2[0].entity_type = 4;
+
+    pldm_entity entity3[1]{};
+    entity3[0].entity_type = 4;
+
+    EXPECT_EQ(pldm_entity_association_pdr_create_new(
+                  repo, 34, &entity2[0], &entity3[0], &updated_record_handle),
+              0);
+
+    EXPECT_EQ(updated_record_handle, 35);
+
+    free(entities);
+    pldm_pdr_destroy(repo);
+    pldm_entity_association_tree_destroy(tree);
+}