Add GPT iterator

To enable a FWU configuration to be derived from the GPT created by
a platform integrator, it is necessary to iterate over GPT entries
and to identify entries that correspond to updatable firmware
partitions. This relies on the ability to read the partition type
guid to detemine the purpose of the partition. This commit adds
an iterator that allows the raw partition table to be iterated
over to make all needed information available to support FWU
configuration.

Signed-off-by: Julian Hall <julian.hall@arm.com>
Change-Id: I283788fa12d999381a2e649d6b4f8ea6aea6b124
diff --git a/components/media/disk/gpt_iterator/component.cmake b/components/media/disk/gpt_iterator/component.cmake
new file mode 100644
index 0000000..a2f74c8
--- /dev/null
+++ b/components/media/disk/gpt_iterator/component.cmake
@@ -0,0 +1,13 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2023, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+if (NOT DEFINED TGT)
+	message(FATAL_ERROR "mandatory parameter TGT is not defined.")
+endif()
+
+target_sources(${TGT} PRIVATE
+	"${CMAKE_CURRENT_LIST_DIR}/gpt_iterator.c"
+)
\ No newline at end of file
diff --git a/components/media/disk/gpt_iterator/gpt_iterator.c b/components/media/disk/gpt_iterator/gpt_iterator.c
new file mode 100644
index 0000000..94a91af
--- /dev/null
+++ b/components/media/disk/gpt_iterator/gpt_iterator.c
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <stddef.h>
+#include <media/volume/volume.h>
+#include "gpt_iterator.h"
+
+
+int gpt_iterator_init(
+	struct gpt_iterator *iter,
+	struct volume *volume)
+{
+	assert(iter);
+	assert(volume);
+
+	iter->num_entries = 0;
+	iter->entry_size = 0;
+	iter->cur_index = 0;
+	iter->volume = NULL;
+
+	int status = volume_open(volume);
+
+	if (status)
+		return status;
+
+	iter->volume = volume;
+
+	status = volume_seek(volume, IO_SEEK_SET, GPT_HEADER_OFFSET);
+
+	if (status)
+		return status;
+
+	gpt_header_t gpt_header;
+	size_t bytes_read = 0;
+
+	status = volume_read(volume, (uintptr_t)&gpt_header, sizeof(gpt_header), &bytes_read);
+
+	if (status)
+		return status;
+
+	if (bytes_read != sizeof(gpt_header))
+		return -EIO;
+
+	/* Check that GPT header looks valid. It is assumed that the CRC will already been checked
+	 * e.g. by the bootloader. The checks are intended to defend against either deliberate or
+	 * accidental corruption of the header leading to use of unreasonable partition table
+	 * attributes. The checks need to be tolerant to GPT version changes that result in a
+	 * mismatch between this code's view of structures and the structure used when the GPT
+	 * in the disk image was created. This could occur through firmware updates over the
+	 * lifetime of the device. An assumption is that over time, GPT structures will only ever
+	 * be extended and that all versions will share a common base structure. The check for an
+	 * oversized partition entry puts a reasonable upper limit on the structure size as 2 *
+	 * sizeof(gpt_entry_t). This relies on there never being a 2x size mismatch over the
+	 * lifetime of the device.
+	 */
+	size_t min_required_entry_size =
+		offsetof(gpt_entry_t, name) + EFI_NAMELEN * sizeof(unsigned short);
+	size_t max_expected_entry_size =
+		sizeof(gpt_entry_t) * 2;
+
+	if ((memcmp(GPT_SIGNATURE, gpt_header.signature, sizeof(gpt_header.signature)) != 0) ||
+		(gpt_header.part_size < min_required_entry_size) ||
+		(gpt_header.part_size > max_expected_entry_size) ||
+		(iter->num_entries > 128))
+		return -EIO;
+
+	iter->num_entries = gpt_header.list_num;
+	iter->entry_size = gpt_header.part_size;
+
+	return 0;
+}
+
+void gpt_iterator_deinit(
+	struct gpt_iterator *iter)
+{
+	assert(iter);
+
+	if (iter->volume)
+		volume_close(iter->volume);
+}
+
+void gpt_iterator_first(
+	struct gpt_iterator *iter)
+{
+	assert(iter);
+
+	iter->cur_index = 0;
+}
+
+void gpt_iterator_next(
+	struct gpt_iterator *iter)
+{
+	assert(iter);
+
+	++iter->cur_index;
+}
+
+bool gpt_iterator_is_done(
+	const struct gpt_iterator *iter)
+{
+	assert(iter);
+
+	return (iter->cur_index >= iter->num_entries);
+}
+
+int gpt_iterator_current(
+	struct gpt_iterator *iter,
+	gpt_entry_t *entry)
+{
+	assert(iter);
+	assert(iter->volume);
+	assert(!gpt_iterator_is_done(iter));
+
+	size_t bytes_read = 0;
+	size_t cur_pos = iter->cur_index * iter->entry_size + GPT_ENTRY_OFFSET;
+	int status = volume_seek(iter->volume, IO_SEEK_SET, cur_pos);
+
+	if (status)
+		return status;
+
+	status = volume_read(iter->volume, (uintptr_t)entry, sizeof(gpt_entry_t), &bytes_read);
+
+	if (status)
+		return status;
+
+	if (bytes_read != sizeof(gpt_entry_t))
+		return -EIO;
+
+	return 0;
+}
diff --git a/components/media/disk/gpt_iterator/gpt_iterator.h b/components/media/disk/gpt_iterator/gpt_iterator.h
new file mode 100644
index 0000000..bb0ebc8
--- /dev/null
+++ b/components/media/disk/gpt_iterator/gpt_iterator.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef GPT_ITERATOR_H
+#define GPT_ITERATOR_H
+
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Export tf-a version with C++ linkage support.
+ */
+#include <drivers/partition/gpt.h>
+
+/**
+ * Interface dependencies
+ */
+struct volume;
+
+/**
+ * \brief gpt_iterator structure definition
+ *
+ * Holds state while iterating over partition table entries
+ */
+struct gpt_iterator {
+
+	unsigned int num_entries;
+	unsigned int entry_size;
+	unsigned int cur_index;
+
+	struct volume *volume;
+};
+
+/**
+ * \brief Initialize the iterator
+ *
+ * Initializes a gpt_iterator in preparation for use. The provided volume
+ * should provide access to the storage device/partition that contains the
+ * MBR/GPT at the start of the volume. The integrity of the GPT should have
+ * already been verified before using this iterator. The volume must be
+ * initially closed. It will be opened on init and closed on deinit.
+ *
+ * \param[in]  iter      The subject gpt_iterator
+ * \param[in]  volume    Volume containing the MBR/GPT
+ *
+ * \return IO Status code (0 for success)
+ */
+int gpt_iterator_init(
+	struct gpt_iterator *iter,
+	struct volume *volume);
+
+/**
+ * \brief De-initialize the iterator
+ *
+ * \param[in]  iter      The subject gpt_iterator
+ */
+void gpt_iterator_deinit(
+	struct gpt_iterator *iter);
+
+/**
+ * \brief Set iterator position to first partition entry
+ *
+ * \param[in]  iter      The subject gpt_iterator
+ */
+void gpt_iterator_first(
+	struct gpt_iterator *iter);
+
+/**
+ * \brief Iterate to the next entry
+ *
+ * \param[in]  iter      The subject gpt_iterator
+ */
+void gpt_iterator_next(
+	struct gpt_iterator *iter);
+
+/**
+ * \brief Returns true if iterated beyond final entry
+ *
+ * \param[in]  iter      The subject gpt_iterator
+ */
+bool gpt_iterator_is_done(
+	const struct gpt_iterator *iter);
+
+/**
+ * \brief Returns the partition entry at the current iterator position
+ *
+ * \param[in]  iter      The subject gpt_iterator
+ * \param[out] entry     Copied to this structure
+ *
+ * \return IO Status code (0 for success)
+ */
+int gpt_iterator_current(
+	struct gpt_iterator *iter,
+	gpt_entry_t *entry);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GPT_ITERATOR_H */
diff --git a/components/media/disk/test/component.cmake b/components/media/disk/test/component.cmake
index ba9f532..993ac94 100644
--- a/components/media/disk/test/component.cmake
+++ b/components/media/disk/test/component.cmake
@@ -1,5 +1,5 @@
 #-------------------------------------------------------------------------------
-# Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+# Copyright (c) 2022-2023, Arm Limited and Contributors. All rights reserved.
 #
 # SPDX-License-Identifier: BSD-3-Clause
 #
@@ -9,5 +9,6 @@
 endif()
 
 target_sources(${TGT} PRIVATE
+	"${CMAKE_CURRENT_LIST_DIR}/gpt_iterator_tests.cpp"
 	"${CMAKE_CURRENT_LIST_DIR}/partition_table_tests.cpp"
 	)
diff --git a/components/media/disk/test/gpt_iterator_tests.cpp b/components/media/disk/test/gpt_iterator_tests.cpp
new file mode 100644
index 0000000..3e16dff
--- /dev/null
+++ b/components/media/disk/test/gpt_iterator_tests.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2023, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <string>
+#include <cstring>
+#include <common/uuid/uuid.h>
+#include <service/block_storage/factory/ref_ram_gpt/block_store_factory.h>
+#include <service/block_storage/config/ref/ref_partition_configurator.h>
+#include <media/volume/index/volume_index.h>
+#include <media/volume/block_volume/block_volume.h>
+#include <media/disk/gpt_iterator/gpt_iterator.h>
+#include <media/disk/guid.h>
+#include <CppUTest/TestHarness.h>
+
+TEST_GROUP(GptIteratorTests)
+{
+	void setup()
+	{
+		volume_index_init();
+
+		/* Create GPT configured block_store using ref partition configuration */
+		m_block_store = ref_ram_gpt_block_store_factory_create();
+		CHECK_TRUE(m_block_store);
+
+		/* Use partition exposed for accessing the disk header */
+		uuid_guid_octets_from_canonical(&m_partition_guid,
+			DISK_GUID_UNIQUE_PARTITION_DISK_HEADER);
+
+		m_volume = NULL;
+
+		int status = block_volume_init(&m_block_volume,
+			m_block_store, &m_partition_guid,
+			&m_volume);
+
+		LONGS_EQUAL(0, status);
+		CHECK_TRUE(m_volume);
+
+		status = gpt_iterator_init(&m_iter, m_volume);
+		LONGS_EQUAL(0, status);
+	}
+
+	void teardown()
+	{
+		gpt_iterator_deinit(&m_iter);
+		block_volume_deinit(&m_block_volume);
+		ref_ram_gpt_block_store_factory_destroy(m_block_store);
+		volume_index_clear();
+	}
+
+	bool check_in_use(const gpt_entry_t *entry)
+	{
+		struct uuid_octets nil_uuid;
+
+		memset(&nil_uuid, 0, sizeof(nil_uuid));
+		return (memcmp(nil_uuid.octets, &entry->type_uuid, sizeof(nil_uuid.octets)) != 0);
+	}
+
+	static const uint32_t CLIENT_ID = 0;
+	static const size_t FIRST_USABLE_LBA = 34;
+
+	struct uuid_octets m_partition_guid;
+	struct block_store *m_block_store;
+	struct block_volume m_block_volume;
+	struct volume *m_volume;
+	struct gpt_iterator m_iter;
+};
+
+TEST(GptIteratorTests, iterateOverRefGpt)
+{
+	/* Expect the reference partition configuration to contain 4 partitions */
+	struct uuid_octets guid;
+	gpt_entry_t gpt_entry;
+	int status;
+
+	/* Set iterator to first entry */
+	gpt_iterator_first(&m_iter);
+	CHECK_FALSE(gpt_iterator_is_done(&m_iter));
+
+	/* Expect to read ref partition 1 */
+	status = gpt_iterator_current(&m_iter, &gpt_entry);
+	LONGS_EQUAL(0, status);
+	CHECK_TRUE(check_in_use(&gpt_entry));
+	uuid_guid_octets_from_canonical(&guid, REF_PARTITION_1_GUID);
+	MEMCMP_EQUAL(guid.octets, (uint8_t *)&gpt_entry.unique_uuid, sizeof(guid.octets));
+	UNSIGNED_LONGS_EQUAL(FIRST_USABLE_LBA + REF_PARTITION_1_STARTING_LBA, gpt_entry.first_lba);
+	UNSIGNED_LONGS_EQUAL((FIRST_USABLE_LBA + REF_PARTITION_1_ENDING_LBA), gpt_entry.last_lba);
+
+	/* Iterate to next */
+	gpt_iterator_next(&m_iter);
+	CHECK_FALSE(gpt_iterator_is_done(&m_iter));
+
+	/* Expect to read ref partition 2 */
+	status = gpt_iterator_current(&m_iter, &gpt_entry);
+	LONGS_EQUAL(0, status);
+	CHECK_TRUE(check_in_use(&gpt_entry));
+	uuid_guid_octets_from_canonical(&guid, REF_PARTITION_2_GUID);
+	MEMCMP_EQUAL(guid.octets, (uint8_t *)&gpt_entry.unique_uuid, sizeof(guid.octets));
+	UNSIGNED_LONGS_EQUAL(FIRST_USABLE_LBA + REF_PARTITION_2_STARTING_LBA, gpt_entry.first_lba);
+	UNSIGNED_LONGS_EQUAL((FIRST_USABLE_LBA + REF_PARTITION_2_ENDING_LBA), gpt_entry.last_lba);
+
+	/* Iterate to next */
+	gpt_iterator_next(&m_iter);
+	CHECK_FALSE(gpt_iterator_is_done(&m_iter));
+
+	/* Expect to read ref partition 3 */
+	status = gpt_iterator_current(&m_iter, &gpt_entry);
+	LONGS_EQUAL(0, status);
+	CHECK_TRUE(check_in_use(&gpt_entry));
+	uuid_guid_octets_from_canonical(&guid, REF_PARTITION_3_GUID);
+	MEMCMP_EQUAL(guid.octets, (uint8_t *)&gpt_entry.unique_uuid, sizeof(guid.octets));
+	UNSIGNED_LONGS_EQUAL(FIRST_USABLE_LBA + REF_PARTITION_3_STARTING_LBA, gpt_entry.first_lba);
+	UNSIGNED_LONGS_EQUAL((FIRST_USABLE_LBA + REF_PARTITION_3_ENDING_LBA), gpt_entry.last_lba);
+
+	/* Iterate to next */
+	gpt_iterator_next(&m_iter);
+	CHECK_FALSE(gpt_iterator_is_done(&m_iter));
+
+	/* Expect to read ref partition 4 */
+	status = gpt_iterator_current(&m_iter, &gpt_entry);
+	LONGS_EQUAL(0, status);
+	CHECK_TRUE(check_in_use(&gpt_entry));
+	uuid_guid_octets_from_canonical(&guid, REF_PARTITION_4_GUID);
+	MEMCMP_EQUAL(guid.octets, (uint8_t *)&gpt_entry.unique_uuid, sizeof(guid.octets));
+	UNSIGNED_LONGS_EQUAL(FIRST_USABLE_LBA + REF_PARTITION_4_STARTING_LBA, gpt_entry.first_lba);
+	UNSIGNED_LONGS_EQUAL((FIRST_USABLE_LBA + REF_PARTITION_4_ENDING_LBA), gpt_entry.last_lba);
+
+	/* Don't expect any other entries to be in-use */
+	gpt_iterator_next(&m_iter);
+
+	while (!gpt_iterator_is_done(&m_iter)) {
+
+		status = gpt_iterator_current(&m_iter, &gpt_entry);
+		LONGS_EQUAL(0, status);
+		CHECK_FALSE(check_in_use(&gpt_entry));
+
+		gpt_iterator_next(&m_iter);
+	}
+}
diff --git a/deployments/component-test/component-test.cmake b/deployments/component-test/component-test.cmake
index 32d4748..f2087d9 100644
--- a/deployments/component-test/component-test.cmake
+++ b/deployments/component-test/component-test.cmake
@@ -166,6 +166,7 @@
 		"components/media/disk"
 		"components/media/disk/disk_images"
 		"components/media/disk/formatter"
+		"components/media/disk/gpt_iterator"
 		"components/media/disk/test"
 		"components/media/volume"
 		"components/media/volume/index"