Add image_directory tests

These tests use the fwu_dut class to construct DUTs with
different firmware store configurations to check that the serialized
image directory correctly reflect the set of updatable components
supported by the DUT.

Signed-off-by: Julian Hall <julian.hall@arm.com>
Change-Id: I4a1d742a4942d22bb5ea747ad057c51f0d8f6643
diff --git a/components/service/fwu/test/ref_scenarios/component.cmake b/components/service/fwu/test/ref_scenarios/component.cmake
new file mode 100644
index 0000000..7f4e143
--- /dev/null
+++ b/components/service/fwu/test/ref_scenarios/component.cmake
@@ -0,0 +1,13 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2022, 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}/image_directory_tests.cpp"
+	)
diff --git a/components/service/fwu/test/ref_scenarios/image_directory_tests.cpp b/components/service/fwu/test/ref_scenarios/image_directory_tests.cpp
new file mode 100644
index 0000000..229a0df
--- /dev/null
+++ b/components/service/fwu/test/ref_scenarios/image_directory_tests.cpp
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <cstdint>
+#include <vector>
+#include <CppUTest/TestHarness.h>
+#include <service/fwu/test/image_directory_checker/image_directory_checker.h>
+#include <service/fwu/test/fwu_dut_factory/fwu_dut_factory.h>
+#include <service/fwu/test/fwu_dut/fwu_dut.h>
+
+/*
+ * Tests that focus on retrieving and checking the contents of the image
+ * directory returned by the update agent. The image directory is intended
+ * to provide an up-to-date view of updatable firmware components.
+ */
+TEST_GROUP(FwuImageDirectoryTests)
+{
+	void setup()
+	{
+		m_dut = NULL;
+		m_fwu_client = NULL;
+	}
+
+	void teardown()
+	{
+		delete m_fwu_client;
+		m_fwu_client = NULL;
+
+		delete m_dut;
+		m_dut = NULL;
+	}
+
+	fwu_dut *m_dut;
+	fwu_client *m_fwu_client;
+};
+
+TEST(FwuImageDirectoryTests, streamedReads)
+{
+	int status = 0;
+
+	/* Construct and boot a DUT with a couple of fw locations */
+	m_dut = fwu_dut_factory::create(2);
+	m_fwu_client = m_dut->create_fwu_client();
+	m_dut->boot();
+
+	/* As a reference, fetch the image directory with the default buffer
+	 * used by an image_directory_checker.
+	 */
+	image_directory_checker checker_a;
+
+	status = checker_a.fetch_image_directory(m_fwu_client);
+	LONGS_EQUAL(0, status);
+
+	/* Construct another image_directory_checker and repeatedly
+	 * fetch and compare the results for consistency.
+	 */
+	image_directory_checker checker_b;
+
+	for (unsigned int i = 0; i < 100; ++i) {
+
+		/* Try lots of reads */
+		status = checker_b.fetch_image_directory(m_fwu_client);
+		LONGS_EQUAL(0, status);
+
+		/* Always expect the read data to be consistent */
+		CHECK_TRUE(checker_a.is_contents_equal(checker_b));
+	}
+}
+
+TEST(FwuImageDirectoryTests, streamRecycling)
+{
+	std::vector<uint32_t> stream_handles;
+	struct uuid_octets uuid;
+
+	uuid_guid_octets_from_canonical(&uuid, FWU_DIRECTORY_CANONICAL_UUID);
+
+	/* Construct and boot a DUT with a couple of fw locations */
+	m_dut = fwu_dut_factory::create(2);
+	m_fwu_client = m_dut->create_fwu_client();
+	m_dut->boot();
+
+	/* Expect to be able to keep opening streams, beyond the capacity of
+	 * the update_agent. The update_agent implements a least recently used
+	 * recycling strategy for streams to defend against a denial-of-service
+	 * attack where streams are opened but never closed.
+	 */
+	for (unsigned int i = 0; i < 200; ++i) {
+
+		int status = 0;
+		uint32_t stream_handle = 0;
+
+		status = m_fwu_client->open(&uuid, &stream_handle);
+		LONGS_EQUAL(0, status);
+
+		stream_handles.push_back(stream_handle);
+	}
+
+	/* Only the most recently opened streams should still be opened. Expect
+	 * older ones to have been cancelled. Test this by closing streams in
+	 * reverse chronological order.
+	 */
+	unsigned int successfully_closed_count = 0;
+	unsigned int cancelled_count = 0;
+	unsigned int stream_index = stream_handles.size();
+
+	CHECK_TRUE(stream_index > 0);
+
+	do {
+
+		int status = 0;
+
+		--stream_index;
+
+		status = m_fwu_client->commit(
+			stream_handles[stream_index],
+			false);
+
+		if (!status) {
+
+			/* Operation successful so expect this to be a recently opened stream */
+			LONGS_EQUAL(0, cancelled_count);
+			++successfully_closed_count;
+		} else {
+
+			/* Operation failed so expect this to be an older stream */
+			CHECK_TRUE(successfully_closed_count > 0);
+			++cancelled_count;
+		}
+	} while (stream_index > 0);
+
+	CHECK_TRUE(successfully_closed_count > 0);
+	CHECK_TRUE(cancelled_count > 0);
+}
+
+TEST(FwuImageDirectoryTests, singleFwLocation)
+{
+	int status = 0;
+
+	/* Construct and boot a DUT with a single fw location. This configuration
+	 * is typical of a TF-A based device where all firmware is loaded from a
+	 * FIP image. A/B banks are stored in separate disk partitions.
+	 */
+	m_dut = fwu_dut_factory::create(1);
+	m_fwu_client = m_dut->create_fwu_client();
+	m_dut->boot();
+
+	image_directory_checker checker;
+
+	status = checker.fetch_image_directory(m_fwu_client);
+	LONGS_EQUAL(0, status);
+
+	const struct ts_fwu_image_directory *dir_header =
+		checker.get_header();
+
+	/* Expect directory header to reflect correct values */
+	CHECK_TRUE(dir_header);
+	UNSIGNED_LONGS_EQUAL(offsetof(struct ts_fwu_image_directory, img_info_entry),
+		dir_header->img_info_offset);
+	UNSIGNED_LONGS_EQUAL(sizeof(struct ts_fwu_image_info_entry),
+		dir_header->img_info_size);
+	UNSIGNED_LONGS_EQUAL(2, dir_header->directory_version);
+	UNSIGNED_LONGS_EQUAL(1, dir_header->correct_boot);
+	UNSIGNED_LONGS_EQUAL(0, dir_header->reserved);
+	CHECK_TRUE(dir_header->num_images >= 1);
+
+	/* Expect an image entry for whole volume updates for location id zero */
+	struct uuid_octets expected_img_type_uuid;
+
+	m_dut->whole_volume_image_type_uuid(0, &expected_img_type_uuid);
+
+	const struct ts_fwu_image_info_entry *image_entry =
+		checker.find_entry(&expected_img_type_uuid);
+
+	CHECK_TRUE(image_entry);
+}
+
+TEST(FwuImageDirectoryTests, multipleFwLocations)
+{
+	int status = 0;
+	unsigned int num_locations = 3;
+
+	/* Construct and boot a DUT with multiple fw locations. This configuration
+	 * will be typical of devices where firmware components distributed
+	 * across multiple disk partitions.
+	 */
+	m_dut = fwu_dut_factory::create(num_locations);
+	m_fwu_client = m_dut->create_fwu_client();
+	m_dut->boot();
+
+	image_directory_checker checker;
+
+	status = checker.fetch_image_directory(m_fwu_client);
+	LONGS_EQUAL(0, status);
+
+	const struct ts_fwu_image_directory *dir_header =
+		checker.get_header();
+
+	/* Expect directory header to reflect correct values */
+	CHECK_TRUE(dir_header);
+	UNSIGNED_LONGS_EQUAL(2, dir_header->directory_version);
+	UNSIGNED_LONGS_EQUAL(1, dir_header->correct_boot);
+	CHECK_TRUE(dir_header->num_images >= num_locations);
+
+	for (unsigned int location_id = 0; location_id < num_locations; location_id++) {
+
+		/* Expect an image entry for whole volume updates for each location */
+		struct uuid_octets expected_img_type_uuid;
+
+		m_dut->whole_volume_image_type_uuid(0, &expected_img_type_uuid);
+
+		const struct ts_fwu_image_info_entry *image_entry =
+			checker.find_entry(&expected_img_type_uuid);
+
+		CHECK_TRUE(image_entry);
+	}
+}
+
+TEST(FwuImageDirectoryTests, zeroFwLocations)
+{
+	int status = 0;
+
+	/* Construct and boot a DUT with no fw locations. This configuration
+	 * will be typical of a device with no Swd accessible flash. With
+	 * this sort of configuration, update images will be installed from
+	 * Nwd.
+	 */
+	m_dut = fwu_dut_factory::create(0);
+	m_fwu_client = m_dut->create_fwu_client();
+	m_dut->boot();
+
+	image_directory_checker checker;
+
+	status = checker.fetch_image_directory(m_fwu_client);
+	LONGS_EQUAL(0, status);
+
+	const struct ts_fwu_image_directory *dir_header =
+		checker.get_header();
+
+	/* Expect directory header to reflect correct values */
+	CHECK_TRUE(dir_header);
+	UNSIGNED_LONGS_EQUAL(2, dir_header->directory_version);
+	UNSIGNED_LONGS_EQUAL(1, dir_header->correct_boot);
+
+	/* The DUT uses a direct_fw_inspector to populate the fw directory.
+	 * This relies on direct access to fw storage to determine what components
+	 * should be reflected in the image directory. In practice, for a single
+	 * flash system, an alternative fw inspector will be used.
+	 */
+	UNSIGNED_LONGS_EQUAL(0, dir_header->num_images);
+}
\ No newline at end of file
diff --git a/deployments/component-test/component-test.cmake b/deployments/component-test/component-test.cmake
index e91db89..465175a 100644
--- a/deployments/component-test/component-test.cmake
+++ b/deployments/component-test/component-test.cmake
@@ -128,6 +128,7 @@
 		"components/service/fwu/test/metadata_checker"
 		"components/service/fwu/test/metadata_fetcher/volume"
 		"components/service/fwu/test/image_directory_checker"
+		"components/service/fwu/test/ref_scenarios"
 		"components/service/crypto/client/cpp"
 		"components/service/crypto/client/cpp/protocol/protobuf"
 		"components/service/crypto/client/cpp/protocol/packed-c"