libpldm: Add APIs related to entity associations

Add APIs for:
- Finding an entity in the association tree
- Extracting entities from an entity association PDR

This is in preparation for the BMC to implement merging of entity
association PDRs received from the host firmware.

Tested: Unit tested.

Signed-off-by: Deepak Kodihalli <dkodihal@in.ibm.com>
Change-Id: I049126570b00df5aab3cd102a54869052f2b43ff
diff --git a/libpldm/pdr.c b/libpldm/pdr.c
index a691163..b29f6f1 100644
--- a/libpldm/pdr.c
+++ b/libpldm/pdr.c
@@ -587,3 +587,76 @@
 
 	entity_association_pdr_add(tree->root, repo);
 }
+
+void entity_association_tree_find(pldm_entity_node *node, pldm_entity *entity,
+				  pldm_entity_node **out)
+{
+	if (node == NULL) {
+		return;
+	}
+
+	if (node->entity.entity_type == entity->entity_type &&
+	    node->entity.entity_instance_num == entity->entity_instance_num) {
+		entity->entity_container_id = node->entity.entity_container_id;
+		*out = node;
+		return;
+	}
+
+	entity_association_tree_find(node->next_sibling, entity, out);
+	entity_association_tree_find(node->first_child, entity, out);
+}
+
+pldm_entity_node *
+pldm_entity_association_tree_find(pldm_entity_association_tree *tree,
+				  pldm_entity *entity)
+{
+	assert(tree != NULL);
+
+	pldm_entity_node *node = NULL;
+	entity_association_tree_find(tree->root, entity, &node);
+	return node;
+}
+
+void pldm_entity_association_pdr_extract(const uint8_t *pdr, uint16_t pdr_len,
+					 size_t *num_entities,
+					 pldm_entity **entities)
+{
+	assert(pdr != NULL);
+	assert(pdr_len >= sizeof(struct pldm_pdr_hdr) +
+			      sizeof(struct pldm_pdr_entity_association));
+
+	struct pldm_pdr_hdr *hdr = (struct pldm_pdr_hdr *)pdr;
+	assert(hdr->type == PLDM_PDR_ENTITY_ASSOCIATION);
+
+	const uint8_t *start = (uint8_t *)pdr;
+	const uint8_t *end =
+	    start + sizeof(struct pldm_pdr_hdr) + le16toh(hdr->length);
+	start += sizeof(struct pldm_pdr_hdr);
+	struct pldm_pdr_entity_association *entity_association_pdr =
+	    (struct pldm_pdr_entity_association *)start;
+	*num_entities = entity_association_pdr->num_children + 1;
+	assert(*num_entities >= 2);
+	*entities = malloc(sizeof(pldm_entity) * *num_entities);
+	assert(*entities != NULL);
+	assert(start + sizeof(struct pldm_pdr_entity_association) +
+		   sizeof(pldm_entity) * (*num_entities - 2) ==
+	       end);
+	(*entities)->entity_type =
+	    le16toh(entity_association_pdr->container.entity_type);
+	(*entities)->entity_instance_num =
+	    le16toh(entity_association_pdr->container.entity_instance_num);
+	(*entities)->entity_container_id =
+	    le16toh(entity_association_pdr->container.entity_container_id);
+	pldm_entity *curr_entity = entity_association_pdr->children;
+	size_t i = 1;
+	while (i < *num_entities) {
+		(*entities + i)->entity_type =
+		    le16toh(curr_entity->entity_type);
+		(*entities + i)->entity_instance_num =
+		    le16toh(curr_entity->entity_instance_num);
+		(*entities + i)->entity_container_id =
+		    le16toh(curr_entity->entity_container_id);
+		++curr_entity;
+		++i;
+	}
+}
diff --git a/libpldm/pdr.h b/libpldm/pdr.h
index c08dc59..c48a559 100644
--- a/libpldm/pdr.h
+++ b/libpldm/pdr.h
@@ -267,6 +267,30 @@
 uint8_t pldm_entity_get_num_children(pldm_entity_node *node,
 				     uint8_t association_type);
 
+/** @brief Find an entity in the entity association tree
+ *
+ *  @param[in] tree - pointer to entity association tree
+ *  @param[in/out] entity - entity type and instance id set on input, container
+ *                 id set on output
+ *
+ *  @return pldm_entity_node* pointer to entity if found, NULL otherwise
+ */
+pldm_entity_node *
+pldm_entity_association_tree_find(pldm_entity_association_tree *tree,
+				  pldm_entity *entity);
+
+/** @brief Extract entities from entity association PDR
+ *
+ *  @param[in] pdr - entity association PDR
+ *  @param[in] pdr_len - size of entity association PDR in bytes
+ *  @param[out] num_entities - number of entities found, including the container
+ *  @param[out] entities - extracted entities, container is *entities[0]. Caller
+ *              must free *entities
+ */
+void pldm_entity_association_pdr_extract(const uint8_t *pdr, uint16_t pdr_len,
+					 size_t *num_entities,
+					 pldm_entity **entities);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/libpldm/tests/libpldm_pdr_test.cpp b/libpldm/tests/libpldm_pdr_test.cpp
index 2f6b58d..b09f542 100644
--- a/libpldm/tests/libpldm_pdr_test.cpp
+++ b/libpldm/tests/libpldm_pdr_test.cpp
@@ -849,3 +849,154 @@
     pldm_pdr_destroy(repo);
     pldm_entity_association_tree_destroy(tree);
 }
+
+TEST(EntityAssociationPDR, testFind)
+{
+    //        1
+    //        |
+    //        2--3--4
+    //        |
+    //        5--6--7
+    //        |  |
+    //        8  9
+
+    pldm_entity entities[9]{};
+
+    entities[0].entity_type = 1;
+    entities[1].entity_type = 2;
+    entities[2].entity_type = 2;
+    entities[3].entity_type = 3;
+    entities[4].entity_type = 4;
+    entities[5].entity_type = 5;
+    entities[6].entity_type = 5;
+    entities[7].entity_type = 6;
+    entities[8].entity_type = 7;
+
+    auto tree = pldm_entity_association_tree_init();
+
+    auto l1 = pldm_entity_association_tree_add(tree, &entities[0], nullptr,
+                                               PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+    EXPECT_NE(l1, nullptr);
+    auto l2a = pldm_entity_association_tree_add(
+        tree, &entities[1], l1, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+    EXPECT_NE(l2a, nullptr);
+    auto l2b = pldm_entity_association_tree_add(
+        tree, &entities[2], l1, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+    EXPECT_NE(l2b, nullptr);
+    auto l2c = pldm_entity_association_tree_add(
+        tree, &entities[3], l1, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+    EXPECT_NE(l2c, nullptr);
+    auto l3a = pldm_entity_association_tree_add(
+        tree, &entities[4], l2a, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+    EXPECT_NE(l3a, nullptr);
+    auto l3b = pldm_entity_association_tree_add(
+        tree, &entities[5], l2a, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+    EXPECT_NE(l3b, nullptr);
+    auto l3c = pldm_entity_association_tree_add(
+        tree, &entities[6], l2a, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+    EXPECT_NE(l3c, nullptr);
+    auto l4a = pldm_entity_association_tree_add(
+        tree, &entities[7], l3a, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+    EXPECT_NE(l4a, nullptr);
+    auto l4b = pldm_entity_association_tree_add(
+        tree, &entities[8], l3b, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+    EXPECT_NE(l4b, nullptr);
+
+    pldm_entity entity{};
+
+    entity.entity_type = 1;
+    entity.entity_instance_num = 1;
+    auto result = pldm_entity_association_tree_find(tree, &entity);
+    EXPECT_EQ(result, l1);
+    EXPECT_EQ(entity.entity_container_id, 0);
+
+    entity.entity_type = 2;
+    entity.entity_instance_num = 1;
+    result = pldm_entity_association_tree_find(tree, &entity);
+    EXPECT_EQ(result, l2a);
+    EXPECT_EQ(entity.entity_container_id, 1);
+    entity.entity_type = 2;
+    entity.entity_instance_num = 2;
+    result = pldm_entity_association_tree_find(tree, &entity);
+    EXPECT_EQ(result, l2b);
+    EXPECT_EQ(entity.entity_container_id, 1);
+    entity.entity_type = 3;
+    entity.entity_instance_num = 1;
+    result = pldm_entity_association_tree_find(tree, &entity);
+    EXPECT_EQ(result, l2c);
+    EXPECT_EQ(entity.entity_container_id, 1);
+
+    entity.entity_type = 7;
+    entity.entity_instance_num = 1;
+    result = pldm_entity_association_tree_find(tree, &entity);
+    EXPECT_EQ(result, l4b);
+    EXPECT_EQ(entity.entity_container_id, 4);
+
+    pldm_entity_association_tree_destroy(tree);
+}
+
+TEST(EntityAssociationPDR, testExtract)
+{
+    std::vector<uint8_t> pdr{};
+    pdr.resize(sizeof(pldm_pdr_hdr) + sizeof(pldm_pdr_entity_association) +
+               sizeof(pldm_entity) * 4);
+    pldm_pdr_hdr* hdr = reinterpret_cast<pldm_pdr_hdr*>(pdr.data());
+    hdr->type = PLDM_PDR_ENTITY_ASSOCIATION;
+    hdr->length =
+        htole16(sizeof(pldm_pdr_entity_association) + sizeof(pldm_entity) * 4);
+
+    pldm_pdr_entity_association* e =
+        reinterpret_cast<pldm_pdr_entity_association*>(pdr.data() +
+                                                       sizeof(pldm_pdr_hdr));
+    e->container_id = htole16(1);
+    e->num_children = 5;
+    e->container.entity_type = htole16(1);
+    e->container.entity_instance_num = htole16(1);
+    e->container.entity_container_id = htole16(0);
+
+    pldm_entity* entity = e->children;
+    entity->entity_type = htole16(2);
+    entity->entity_instance_num = htole16(1);
+    entity->entity_container_id = htole16(1);
+    ++entity;
+    entity->entity_type = htole16(3);
+    entity->entity_instance_num = htole16(1);
+    entity->entity_container_id = htole16(1);
+    ++entity;
+    entity->entity_type = htole16(4);
+    entity->entity_instance_num = htole16(1);
+    entity->entity_container_id = htole16(1);
+    ++entity;
+    entity->entity_type = htole16(5);
+    entity->entity_instance_num = htole16(1);
+    entity->entity_container_id = htole16(1);
+    ++entity;
+    entity->entity_type = htole16(6);
+    entity->entity_instance_num = htole16(1);
+    entity->entity_container_id = htole16(1);
+
+    size_t num{};
+    pldm_entity* out = nullptr;
+    pldm_entity_association_pdr_extract(pdr.data(), pdr.size(), &num, &out);
+    EXPECT_EQ(num, e->num_children + 1);
+    EXPECT_EQ(out[0].entity_type, 1);
+    EXPECT_EQ(out[0].entity_instance_num, 1);
+    EXPECT_EQ(out[0].entity_container_id, 0);
+    EXPECT_EQ(out[1].entity_type, 2);
+    EXPECT_EQ(out[1].entity_instance_num, 1);
+    EXPECT_EQ(out[1].entity_container_id, 1);
+    EXPECT_EQ(out[2].entity_type, 3);
+    EXPECT_EQ(out[2].entity_instance_num, 1);
+    EXPECT_EQ(out[2].entity_container_id, 1);
+    EXPECT_EQ(out[3].entity_type, 4);
+    EXPECT_EQ(out[3].entity_instance_num, 1);
+    EXPECT_EQ(out[3].entity_container_id, 1);
+    EXPECT_EQ(out[4].entity_type, 5);
+    EXPECT_EQ(out[4].entity_instance_num, 1);
+    EXPECT_EQ(out[4].entity_container_id, 1);
+    EXPECT_EQ(out[5].entity_type, 6);
+    EXPECT_EQ(out[5].entity_instance_num, 1);
+    EXPECT_EQ(out[5].entity_container_id, 1);
+
+    free(out);
+}