Add fwu raw_installer

The raw_installer can be used to install a raw image directly into
a storage volume. The raw_installer can be used for say installing
a whole firmware update contained within a single image into target
storage such as a flash partition.

Signed-off-by: Julian Hall <julian.hall@arm.com>
Change-Id: I78de31ffdd8185adaf91c98865f457776cada2a5
diff --git a/components/service/fwu/installer/raw/component.cmake b/components/service/fwu/installer/raw/component.cmake
new file mode 100644
index 0000000..47c4cbb
--- /dev/null
+++ b/components/service/fwu/installer/raw/component.cmake
@@ -0,0 +1,16 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2022-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}/raw_installer.c"
+	)
+
+target_compile_definitions(${TGT} PRIVATE
+	RAW_INSTALLER_AVAILABLE)
\ No newline at end of file
diff --git a/components/service/fwu/installer/raw/raw_installer.c b/components/service/fwu/installer/raw/raw_installer.c
new file mode 100644
index 0000000..55cdcff
--- /dev/null
+++ b/components/service/fwu/installer/raw/raw_installer.c
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2022, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#include <assert.h>
+#include <stddef.h>
+#include <string.h>
+#include <media/volume/index/volume_index.h>
+#include <service/fwu/agent/fw_directory.h>
+#include <protocols/service/fwu/packed-c/status.h>
+#include "raw_installer.h"
+
+
+static int raw_installer_begin(void *context,
+	unsigned int current_volume_id,
+	unsigned int update_volume_id)
+{
+	struct raw_installer *subject = (struct raw_installer *)context;
+
+	(void)current_volume_id;
+
+	int status = volume_index_find(
+		update_volume_id,
+		&subject->target_volume);
+
+	if (status == 0) {
+
+		assert(subject->target_volume);
+
+		subject->commit_count = 0;
+		subject->is_open = false;
+	}
+
+	return status;
+}
+
+static int raw_installer_finalize(void *context)
+{
+	struct raw_installer *subject = (struct raw_installer *)context;
+
+	/* Close volume if left open */
+	if (subject->is_open) {
+
+		assert(subject->target_volume);
+
+		volume_close(subject->target_volume);
+		subject->is_open = false;
+	}
+
+	return FWU_STATUS_SUCCESS;
+}
+
+static void raw_installer_abort(void *context)
+{
+	raw_installer_finalize(context);
+}
+
+static int raw_installer_open(void *context,
+	const struct image_info *image_info)
+{
+	struct raw_installer *subject = (struct raw_installer *)context;
+	int status = FWU_STATUS_DENIED;
+
+	/* Because the raw_installer uses a single image to update the
+	 * target volume, it only makes sense to commit a single image
+	 * during an update transaction. Defend against the case where
+	 * an input update package contains more than one raw image to
+	 * install into a particular location.
+	 */
+	if (!subject->is_open && subject->commit_count < 1) {
+
+		assert(subject->target_volume);
+
+		status = volume_open(subject->target_volume);
+
+		if (!status) {
+
+			/* Prior to writing to the volume to install the image, ensure
+			 * that the volume is erased.
+			 */
+			status = volume_erase(subject->target_volume);
+
+			if (!status) {
+
+				subject->is_open = true;
+				subject->bytes_written = 0;
+			} else {
+				/* Failed to erase */
+				volume_close(subject->target_volume);
+			}
+		}
+	}
+
+	return status;
+}
+
+static int raw_installer_commit(void *context)
+{
+	struct raw_installer *subject = (struct raw_installer *)context;
+	int status = FWU_STATUS_DENIED;
+
+	if (subject->is_open) {
+
+		assert(subject->target_volume);
+
+		status = volume_close(subject->target_volume);
+
+		++subject->commit_count;
+		subject->is_open = false;
+
+		if (!status && !subject->bytes_written) {
+
+			/* Installing a zero length image can imply an image delete
+			 * operation. For certain types of installer, this is a legitimate
+			 * operation. For a raw_installer, there really is no way to
+			 * delete an image so return an error if an attempt was made.
+			 */
+			status = FWU_STATUS_NOT_AVAILABLE;
+		}
+	}
+
+	return status;
+}
+
+static int raw_installer_write(void *context,
+	const uint8_t *data,
+	size_t data_len)
+{
+	struct raw_installer *subject = (struct raw_installer *)context;
+	int status = FWU_STATUS_DENIED;
+
+	if (subject->is_open) {
+
+		assert(subject->target_volume);
+
+		size_t len_written = 0;
+
+		status = volume_write(subject->target_volume,
+			(const uintptr_t)data, data_len,
+			&len_written);
+
+		subject->bytes_written += len_written;
+
+		/* Check for the volume full condition where not all the requested
+		 * data was written.
+		 */
+		if (!status && (len_written != data_len))
+			status = FWU_STATUS_OUT_OF_BOUNDS;
+	}
+
+	return status;
+}
+
+static int raw_installer_enumerate(void *context,
+	uint32_t volume_id,
+	struct fw_directory *fw_directory)
+{
+	struct raw_installer *subject = (struct raw_installer *)context;
+	struct volume *volume = NULL;
+
+	int status = volume_index_find(volume_id, &volume);
+
+	if (status != 0)
+		return status;
+
+	assert(volume);
+
+	/* Found the active volume so query it for information in order to
+	 * prepare an entry in the fw_directory to represent the whole volume
+	 * as an advertised updatable image.
+	 */
+	struct image_info image_info = {0};
+
+	/* Limit the advertised max size to the volume size. The volume needs
+	 * to be open to query its size.
+	 */
+	if (!subject->is_open) {
+		/* Open if necessary */
+		status = volume_open(volume);
+		if (status != 0)
+			return status;
+	}
+
+	status = volume_size(volume, &image_info.max_size);
+	if (status != 0)
+		return status;
+
+	if (!subject->is_open) {
+		/* Leave volume in the same open state */
+		status = volume_close(volume);
+		if (status)
+			return status;
+	}
+
+	/* These attributes will have been assigned during platform configuration */
+	image_info.img_type_uuid = subject->base_installer.location_uuid;
+	image_info.location_id = subject->base_installer.location_id;
+	image_info.install_type = subject->base_installer.install_type;
+
+	status = fw_directory_add_image_info(fw_directory, &image_info);
+
+	return status;
+}
+
+void raw_installer_init(struct raw_installer *subject,
+	const struct uuid_octets *location_uuid,
+	uint32_t location_id)
+{
+	/* Define concrete installer interface */
+	static const struct installer_interface interface = {
+		raw_installer_begin,
+		raw_installer_finalize,
+		raw_installer_abort,
+		raw_installer_open,
+		raw_installer_commit,
+		raw_installer_write,
+		raw_installer_enumerate
+	};
+
+	/* Initialize base installer - a raw_installer is a type of
+	 * installer that always updates a whole volume.
+	 */
+	installer_init(&subject->base_installer,
+		INSTALL_TYPE_WHOLE_VOLUME,
+		location_id,
+		location_uuid,
+		subject, &interface);
+
+	/* Initialize raw_installer specifics */
+	subject->target_volume = NULL;
+	subject->commit_count = 0;
+	subject->bytes_written = 0;
+	subject->is_open = false;
+}
+
+void raw_installer_deinit(struct raw_installer *subject)
+{
+	(void)subject;
+}
diff --git a/components/service/fwu/installer/raw/raw_installer.h b/components/service/fwu/installer/raw/raw_installer.h
new file mode 100644
index 0000000..d82cbc2
--- /dev/null
+++ b/components/service/fwu/installer/raw/raw_installer.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2022, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef FWU_RAW_INSTALLER_H
+#define FWU_RAW_INSTALLER_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <common/uuid/uuid.h>
+#include <service/fwu/installer/installer.h>
+#include <media/volume/volume.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \brief raw_installer structure definition
+ *
+ * A raw_installer is an installer that takes the raw input image and writes
+ * it directly to the target storage volume with no intermediate processing.
+ * The raw_installer can be used for say installing a complete firmware image
+ * into a single volume. Because the raw_installer has no knowledge of the
+ * format of images that it installs, the assigned location_uuid is used
+ * by the enumerate method to advertise a single updatable image that
+ * corresponds to the entire raw contents of the associated target
+ * volume. Other sub-volume installers may advertise additional updatable
+ * images that reside within the same target volume.
+ */
+struct raw_installer {
+	struct installer base_installer;
+	struct volume *target_volume;
+	unsigned int commit_count;
+	size_t bytes_written;
+	bool is_open;
+};
+
+/**
+ * \brief Initialize a raw_installer
+ *
+ * \param[in]  subject    The subject raw_installer
+ * \param[in]  location_uuid The associated location UUID
+ * \param[in]  location_id Identifies where to install qualifying images
+ */
+void raw_installer_init(struct raw_installer *subject,
+	const struct uuid_octets *location_uuid,
+	uint32_t location_id);
+
+/**
+ * \brief De-initialize a raw_installer
+ *
+ * \param[in]  subject    The subject raw_installer
+ */
+void raw_installer_deinit(struct raw_installer *subject);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FWU_RAW_INSTALLER_H */
diff --git a/components/service/fwu/installer/raw/test/component.cmake b/components/service/fwu/installer/raw/test/component.cmake
new file mode 100644
index 0000000..adda59c
--- /dev/null
+++ b/components/service/fwu/installer/raw/test/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}/raw_installer_tests.cpp"
+	)
diff --git a/components/service/fwu/installer/raw/test/raw_installer_tests.cpp b/components/service/fwu/installer/raw/test/raw_installer_tests.cpp
new file mode 100644
index 0000000..e0c1c6a
--- /dev/null
+++ b/components/service/fwu/installer/raw/test/raw_installer_tests.cpp
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <cstdlib>
+#include <cstring>
+#include <common/uuid/uuid.h>
+#include <media/disk/guid.h>
+#include <media/volume/block_volume/block_volume.h>
+#include <media/volume/index/volume_index.h>
+#include <media/volume/volume.h>
+#include <service/block_storage/factory/ref_ram_gpt/block_store_factory.h>
+#include <service/block_storage/config/ref/ref_partition_configurator.h>
+#include <service/fwu/installer/raw/raw_installer.h>
+#include <service/fwu/installer/installer_index.h>
+#include <service/fwu/fw_store/banked/volume_id.h>
+#include <service/fwu/agent/fw_directory.h>
+#include <CppUTest/TestHarness.h>
+
+TEST_GROUP(FwuRawInstallerTests)
+{
+	void setup()
+	{
+		int result;
+		struct uuid_octets partition_guid;
+
+		m_image = NULL;
+
+		installer_index_init();
+		volume_index_init();
+
+		/* Use the reference disk configuration and use partition 1 & 2 as
+		 * storage for A and B firmware banks.
+		 */
+		m_block_store = ref_ram_gpt_block_store_factory_create();
+
+		/* Construct fw volume A */
+		uuid_guid_octets_from_canonical(&partition_guid, REF_PARTITION_1_GUID);
+
+		result = block_volume_init(&m_block_volume_a,
+			m_block_store, &partition_guid,
+			&m_fw_volume_a);
+
+		LONGS_EQUAL(0, result);
+		CHECK_TRUE(m_fw_volume_a);
+
+		/* Construct fw volume B */
+		uuid_guid_octets_from_canonical(&partition_guid, REF_PARTITION_2_GUID);
+
+		result = block_volume_init(&m_block_volume_b,
+			m_block_store, &partition_guid,
+			&m_fw_volume_b);
+
+		LONGS_EQUAL(0, result);
+		CHECK_TRUE(m_fw_volume_b);
+
+		/* Prepare an image_info structure to describe the image to
+		 * install. In a complete integration, this will come from the
+		 * fw_directory.
+		 */
+		uuid_guid_octets_from_canonical(&m_image_info.img_type_uuid,
+			"1c22ca2c-9732-49e6-ba3b-eed40e27fda3");
+
+		m_image_info.max_size =
+			(REF_PARTITION_1_ENDING_LBA - REF_PARTITION_1_STARTING_LBA + 1) *
+			REF_PARTITION_BLOCK_SIZE;
+		m_image_info.lowest_accepted_version = 1;
+		m_image_info.active_version = 1;
+		m_image_info.permissions = 0;
+		m_image_info.image_index = 0;
+		m_image_info.location_id = FW_STORE_LOCATION_ID;
+		m_image_info.install_type = INSTALL_TYPE_WHOLE_VOLUME;
+
+		/* Mimic a platform configuration where storage volumes are assigned
+		 * location and usage IDs. These usage IDs correspond to an A/B banked
+		 * firmware store. Multiple locations could be configured here
+		 * but for these tests, there is only one. This enables the generic
+		 * banked_fw_store to be completely decoupled from the details
+		 * of which fw volumes need updating on a platform.
+		 */
+		volume_index_add(
+			banked_volume_id(FW_STORE_LOCATION_ID, BANKED_USAGE_ID_FW_BANK_A),
+			m_fw_volume_a);
+		volume_index_add(
+			banked_volume_id(FW_STORE_LOCATION_ID, BANKED_USAGE_ID_FW_BANK_B),
+			m_fw_volume_b);
+
+		/* A platform configuration will also determine which installers are
+		 * assigned to which locations. This provides flexibility to configure
+		 * appropriate installers to handle alternative fw packages and installation
+		 * strategies.
+		 */
+		raw_installer_init(&m_installer,
+			&m_image_info.img_type_uuid, FW_STORE_LOCATION_ID);
+
+		installer_index_register(&m_installer.base_installer);
+	}
+
+	void teardown()
+	{
+		delete[] m_image;
+
+		raw_installer_deinit(&m_installer);
+
+		installer_index_clear();
+		volume_index_clear();
+
+		block_volume_deinit(&m_block_volume_a);
+		block_volume_deinit(&m_block_volume_b);
+		ref_ram_gpt_block_store_factory_destroy(m_block_store);
+	}
+
+	void create_image(size_t len)
+	{
+		m_image = new uint8_t[len];
+		m_image_len = len;
+
+		for (size_t i = 0; i < len; i++)
+			m_image[i] = (uint8_t)rand();
+	}
+
+	int write_image_in_random_len_chunks(struct installer *installer)
+	{
+		int status = 0;
+		size_t bytes_written = 0;
+
+		while ((bytes_written < m_image_len) && !status) {
+
+			size_t write_len = rand() % 100 + 1;
+
+			if ((bytes_written + write_len) > m_image_len)
+				write_len = m_image_len - bytes_written;
+
+			status = installer_write(installer, &m_image[bytes_written], write_len);
+			bytes_written += write_len;
+		}
+
+		return status;
+	}
+
+	void check_update_installed(struct volume *volume)
+	{
+		int status = 0;
+		size_t total_read = 0;
+		uintptr_t file_handle = 0;
+
+		status = io_open(volume->dev_handle, volume->io_spec, &file_handle);
+		LONGS_EQUAL(0, status);
+
+		while (total_read < m_image_len) {
+
+			uint8_t read_buf[1000];
+			size_t len_read = 0;
+			size_t bytes_remaining = m_image_len - total_read;
+			size_t req_len = (bytes_remaining > sizeof(read_buf)) ?
+				sizeof(read_buf) : bytes_remaining;
+
+			memset(read_buf, 0, sizeof(read_buf));
+
+			status = io_read(file_handle,
+				(uintptr_t)read_buf, req_len,
+				&len_read);
+			LONGS_EQUAL(0, status);
+			UNSIGNED_LONGS_EQUAL(req_len, len_read);
+
+			MEMCMP_EQUAL(&m_image[total_read], read_buf, len_read);
+
+			total_read += len_read;
+		}
+
+		io_close(file_handle);
+	}
+
+	static const unsigned int FW_STORE_LOCATION_ID = 0x100;
+
+	struct block_store *m_block_store;
+	struct block_volume m_block_volume_a;
+	struct block_volume m_block_volume_b;
+	struct volume *m_fw_volume_a;
+	struct volume *m_fw_volume_b;
+	struct raw_installer m_installer;
+	struct image_info m_image_info;
+	uint8_t *m_image;
+	size_t m_image_len;
+};
+
+TEST(FwuRawInstallerTests, normalInstallFlow)
+{
+	int status = 0;
+
+	/* Create arbitrary length image that should easily fit into the destination partition */
+	create_image(REF_PARTITION_BLOCK_SIZE * 2 + 111);
+
+	/* Expect to find a suitable installer for the given image_info */
+	struct installer *installer = installer_index_find(
+		m_image_info.install_type, m_image_info.location_id);
+	CHECK_TRUE(installer);
+	UNSIGNED_LONGS_EQUAL(FW_STORE_LOCATION_ID, installer->location_id);
+
+	/* Begin installation transaction - installing into volume A */
+	status = installer_begin(installer,
+		banked_volume_id(FW_STORE_LOCATION_ID,
+			BANKED_USAGE_ID_FW_BANK_B),  /* Current volume */
+		banked_volume_id(FW_STORE_LOCATION_ID,
+			BANKED_USAGE_ID_FW_BANK_A)); /* Update volume */
+	LONGS_EQUAL(0, status);
+
+	/* Open install stream */
+	status = installer_open(installer, &m_image_info);
+	LONGS_EQUAL(0, status);
+
+	/* Stream the update image into the installer */
+	status = write_image_in_random_len_chunks(installer);
+	LONGS_EQUAL(0, status);
+
+	/* Commit after writing all image data */
+	status = installer_commit(installer);
+	LONGS_EQUAL(0, status);
+
+	/* Finalize installation transaction */
+	status = installer_finalize(installer);
+	LONGS_EQUAL(0, status);
+
+	/* Expect the update volume to contain the update */
+	check_update_installed(m_fw_volume_a);
+}
diff --git a/deployments/component-test/component-test.cmake b/deployments/component-test/component-test.cmake
index e6a8cef..f0aaa8c 100644
--- a/deployments/component-test/component-test.cmake
+++ b/deployments/component-test/component-test.cmake
@@ -113,6 +113,8 @@
 		"components/service/fwu/fw_store/banked/metadata_serializer/v1"
 		"components/service/fwu/fw_store/banked/test"
 		"components/service/fwu/installer"
+		"components/service/fwu/installer/raw"
+		"components/service/fwu/installer/raw/test"
 		"components/service/fwu/inspector/mock"
 		"components/service/crypto/client/cpp"
 		"components/service/crypto/client/cpp/protocol/protobuf"