Add `struct string` for strings parsed from FDT

Just a small wrapper around a char array to make things simpler.

Bug: 117551352
Change-Id: I0880ebbb81258830290ce49bf3772280551d3483
diff --git a/src/BUILD.gn b/src/BUILD.gn
index 964509e..1b81c4a 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -52,6 +52,7 @@
     "manifest.c",
     "panic.c",
     "spci_architected_message.c",
+    "string.c",
     "vm.c",
   ]
 
@@ -158,6 +159,7 @@
     "mm_test.cc",
     "mpool_test.cc",
     "spci_test.cc",
+    "string_test.cc",
   ]
   sources += [ "layout_fake.c" ]
   cflags_cc = [
diff --git a/src/boot_flow/linux.c b/src/boot_flow/linux.c
index f4f34e0..9d4d886 100644
--- a/src/boot_flow/linux.c
+++ b/src/boot_flow/linux.c
@@ -14,34 +14,13 @@
  * limitations under the License.
  */
 
+#include "hf/check.h"
 #include "hf/cpio.h"
 #include "hf/dlog.h"
 #include "hf/fdt_handler.h"
 #include "hf/plat/boot_flow.h"
 #include "hf/std.h"
 
-/**
- * Looks for a file in the given cpio archive. The file, if found, is returned
- * in the "it" argument.
- */
-static bool find_file(const struct memiter *cpio, const char *name,
-		      struct memiter *it)
-{
-	const char *fname;
-	const void *fcontents;
-	size_t fsize;
-	struct memiter iter = *cpio;
-
-	while (cpio_next(&iter, &fname, &fcontents, &fsize)) {
-		if (!strcmp(fname, name)) {
-			memiter_init(it, fcontents, fsize);
-			return true;
-		}
-	}
-
-	return false;
-}
-
 /* Set by arch-specific boot-time hook. */
 uintreg_t plat_boot_flow_fdt_addr;
 
@@ -76,9 +55,10 @@
 			   struct boot_params_update *update,
 			   struct memiter *cpio, struct mpool *ppool)
 {
+	static struct string filename = STRING_INIT("initrd.img");
 	struct memiter primary_initrd;
 
-	if (!find_file(cpio, "initrd.img", &primary_initrd)) {
+	if (!cpio_get_file(cpio, &filename, &primary_initrd)) {
 		dlog("Unable to find initrd.img\n");
 		return false;
 	}
diff --git a/src/cpio.c b/src/cpio.c
index 61a82d6..58626ec 100644
--- a/src/cpio.c
+++ b/src/cpio.c
@@ -41,8 +41,8 @@
  * advances the iterator such that another call to this function would return
  * the following file.
  */
-bool cpio_next(struct memiter *iter, const char **name, const void **contents,
-	       size_t *size)
+static bool cpio_next(struct memiter *iter, const char **name,
+		      const void **contents, size_t *size)
 {
 	size_t len;
 	struct memiter lit = *iter;
@@ -80,3 +80,25 @@
 
 	return true;
 }
+
+/**
+ * Looks for a file in the given cpio archive. The file, if found, is returned
+ * in the "it" argument.
+ */
+bool cpio_get_file(const struct memiter *cpio, const struct string *name,
+		   struct memiter *it)
+{
+	const char *fname;
+	const void *fcontents;
+	size_t fsize;
+	struct memiter iter = *cpio;
+
+	while (cpio_next(&iter, &fname, &fcontents, &fsize)) {
+		if (!strcmp(fname, string_data(name))) {
+			memiter_init(it, fcontents, fsize);
+			return true;
+		}
+	}
+
+	return false;
+}
diff --git a/src/load.c b/src/load.c
index fd4bff3..7138824 100644
--- a/src/load.c
+++ b/src/load.c
@@ -61,48 +61,20 @@
 	return true;
 }
 
-/**
- * Looks for a file in the given cpio archive. The filename is not
- * null-terminated, so we use a memory iterator to represent it. The file, if
- * found, is returned in the "it" argument.
- */
-static bool find_file(const struct memiter *cpio,
-		      const struct memiter *filename, struct memiter *it)
-{
-	const char *fname;
-	const void *fcontents;
-	size_t fsize;
-	struct memiter iter = *cpio;
-
-	while (cpio_next(&iter, &fname, &fcontents, &fsize)) {
-		if (memiter_iseq(filename, fname)) {
-			memiter_init(it, fcontents, fsize);
-			return true;
-		}
-	}
-
-	return false;
-}
-
 static bool load_kernel(struct mm_stage1_locked stage1_locked, paddr_t begin,
 			paddr_t end, const struct manifest_vm *manifest_vm,
 			const struct memiter *cpio, struct mpool *ppool)
 {
-	struct memiter kernel_filename;
 	struct memiter kernel;
 
-	memiter_init(&kernel_filename, manifest_vm->kernel_filename,
-		     strnlen_s(manifest_vm->kernel_filename,
-			       MANIFEST_MAX_STRING_LENGTH));
-
-	if (memiter_size(&kernel_filename) == 0) {
+	if (string_is_empty(&manifest_vm->kernel_filename)) {
 		/* This signals the kernel has been preloaded. */
 		return true;
 	}
 
-	if (!find_file(cpio, &kernel_filename, &kernel)) {
+	if (!cpio_get_file(cpio, &manifest_vm->kernel_filename, &kernel)) {
 		dlog("Could not find kernel file \"%s\".\n",
-		     manifest_vm->kernel_filename);
+		     string_data(&manifest_vm->kernel_filename));
 		return false;
 	}
 
diff --git a/src/manifest.c b/src/manifest.c
index 80382ee..6d8b4ba 100644
--- a/src/manifest.c
+++ b/src/manifest.c
@@ -53,29 +53,9 @@
 	return ptr;
 }
 
-static enum manifest_return_code extract_string(const char *data, uint32_t size,
-						char *out, rsize_t out_sz)
-{
-	/*
-	 * Require that the value contains exactly one NULL character and that
-	 * it is the last byte.
-	 */
-	if (memchr(data, '\0', size) != &data[size - 1]) {
-		return MANIFEST_ERROR_MALFORMED_STRING;
-	}
-
-	/* Check that the string fits into the buffer. */
-	if (size > out_sz) {
-		return MANIFEST_ERROR_STRING_TOO_LONG;
-	}
-
-	memcpy_s(out, out_sz, data, size);
-	return MANIFEST_SUCCESS;
-}
-
 static enum manifest_return_code read_string(const struct fdt_node *node,
-					     const char *property, char *out,
-					     rsize_t out_sz)
+					     const char *property,
+					     struct string *out)
 {
 	const char *data;
 	uint32_t size;
@@ -84,26 +64,27 @@
 		return MANIFEST_ERROR_PROPERTY_NOT_FOUND;
 	}
 
-	return extract_string(data, size, out, out_sz);
+	switch (string_init(out, data, size)) {
+	case STRING_SUCCESS:
+		return MANIFEST_SUCCESS;
+	case STRING_ERROR_INVALID_INPUT:
+		return MANIFEST_ERROR_MALFORMED_STRING;
+	case STRING_ERROR_TOO_LONG:
+		return MANIFEST_ERROR_STRING_TOO_LONG;
+	}
 }
 
 static enum manifest_return_code read_optional_string(
-	const struct fdt_node *node, const char *property, char *out,
-	rsize_t out_sz)
+	const struct fdt_node *node, const char *property, struct string *out)
 {
-	const char *data;
-	uint32_t size;
+	enum manifest_return_code ret;
 
-	if (!fdt_read_property(node, property, &data, &size)) {
-		if (out_sz < 1) {
-			return MANIFEST_ERROR_STRING_TOO_LONG;
-		}
-
-		*out = '\0';
-		return MANIFEST_SUCCESS;
+	ret = read_string(node, property, out);
+	if (ret == MANIFEST_ERROR_PROPERTY_NOT_FOUND) {
+		string_init_empty(out);
+		ret = MANIFEST_SUCCESS;
 	}
-
-	return extract_string(data, size, out, out_sz);
+	return ret;
 }
 
 static enum manifest_return_code read_uint64(const struct fdt_node *node,
@@ -225,10 +206,9 @@
 					  struct manifest_vm *vm,
 					  spci_vm_id_t vm_id)
 {
-	TRY(read_string(node, "debug_name", vm->debug_name,
-			sizeof(vm->debug_name)));
-	TRY(read_optional_string(node, "kernel_filename", vm->kernel_filename,
-				 sizeof(vm->kernel_filename)));
+	TRY(read_string(node, "debug_name", &vm->debug_name));
+	TRY(read_optional_string(node, "kernel_filename",
+				 &vm->kernel_filename));
 	if (vm_id != HF_PRIMARY_VM_ID) {
 		TRY(read_uint64(node, "mem_size", &vm->secondary.mem_size));
 		TRY(read_uint16(node, "vcpu_count", &vm->secondary.vcpu_count));
diff --git a/src/manifest_test.cc b/src/manifest_test.cc
index 14eef49..d7690b4 100644
--- a/src/manifest_test.cc
+++ b/src/manifest_test.cc
@@ -304,8 +304,8 @@
 {
 	const char last_valid[] = "1234567890123456789012345678901";
 	const char first_invalid[] = "12345678901234567890123456789012";
-	static_assert(sizeof(last_valid) == MANIFEST_MAX_STRING_LENGTH);
-	static_assert(sizeof(first_invalid) == MANIFEST_MAX_STRING_LENGTH + 1);
+	static_assert(sizeof(last_valid) == STRING_MAX_SIZE);
+	static_assert(sizeof(first_invalid) == STRING_MAX_SIZE + 1);
 
 	/* clang-format off */
 	return ManifestDtBuilder()
@@ -433,18 +433,19 @@
 	ASSERT_EQ(m.vm_count, 3);
 
 	vm = &m.vm[0];
-	ASSERT_STREQ(vm->debug_name, "primary_vm");
-	ASSERT_STREQ(vm->kernel_filename, "primary_kernel");
+	ASSERT_STREQ(string_data(&vm->debug_name), "primary_vm");
+	ASSERT_STREQ(string_data(&vm->kernel_filename), "primary_kernel");
 
 	vm = &m.vm[1];
-	ASSERT_STREQ(vm->debug_name, "first_secondary_vm");
-	ASSERT_STREQ(vm->kernel_filename, "");
+	ASSERT_STREQ(string_data(&vm->debug_name), "first_secondary_vm");
+	ASSERT_STREQ(string_data(&vm->kernel_filename), "");
 	ASSERT_EQ(vm->secondary.vcpu_count, 42);
 	ASSERT_EQ(vm->secondary.mem_size, 12345);
 
 	vm = &m.vm[2];
-	ASSERT_STREQ(vm->debug_name, "second_secondary_vm");
-	ASSERT_STREQ(vm->kernel_filename, "second_secondary_kernel");
+	ASSERT_STREQ(string_data(&vm->debug_name), "second_secondary_vm");
+	ASSERT_STREQ(string_data(&vm->kernel_filename),
+		     "second_secondary_kernel");
 	ASSERT_EQ(vm->secondary.vcpu_count, 43);
 	ASSERT_EQ(vm->secondary.mem_size, 0x12345);
 }
diff --git a/src/string.c b/src/string.c
new file mode 100644
index 0000000..4876a3f
--- /dev/null
+++ b/src/string.c
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2019 The Hafnium Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "hf/string.h"
+
+#include "hf/static_assert.h"
+#include "hf/std.h"
+
+void string_init_empty(struct string *str)
+{
+	static_assert(sizeof(str->data) >= 1, "String buffer too small");
+	str->data[0] = '\0';
+}
+
+/**
+ * Caller must guarantee that `data` points to a NULL-terminated string.
+ * The constructor checks that it fits into the internal buffer and copies
+ * the string there.
+ */
+enum string_return_code string_init(struct string *str, const char *data,
+				    size_t size)
+{
+	/*
+	 * Require that the value contains exactly one NULL character and that
+	 * it is the last byte.
+	 */
+	if (size < 1 || memchr(data, '\0', size) != &data[size - 1]) {
+		return STRING_ERROR_INVALID_INPUT;
+	}
+
+	if (size > sizeof(str->data)) {
+		return STRING_ERROR_TOO_LONG;
+	}
+
+	memcpy_s(str->data, sizeof(str->data), data, size);
+	return STRING_SUCCESS;
+}
+
+bool string_is_empty(const struct string *str)
+{
+	return str->data[0] == '\0';
+}
+
+const char *string_data(const struct string *str)
+{
+	return str->data;
+}
diff --git a/src/string_test.cc b/src/string_test.cc
new file mode 100644
index 0000000..f814636
--- /dev/null
+++ b/src/string_test.cc
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2019 The Hafnium Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+
+extern "C" {
+#include "hf/string.h"
+}
+
+namespace
+{
+TEST(string, valid)
+{
+	struct string str;
+	constexpr const char data[] = "test";
+
+	string_init_empty(&str);
+	ASSERT_TRUE(string_is_empty(&str));
+	ASSERT_STREQ(string_data(&str), "");
+
+	ASSERT_EQ(string_init(&str, data, sizeof(data)), STRING_SUCCESS);
+	ASSERT_FALSE(string_is_empty(&str));
+	ASSERT_STRNE(string_data(&str), "");
+	ASSERT_STREQ(string_data(&str), "test");
+}
+
+TEST(string, data_zero_size)
+{
+	struct string str;
+	constexpr const char data[] = "test";
+
+	ASSERT_EQ(string_init(&str, data, 0), STRING_ERROR_INVALID_INPUT);
+}
+
+TEST(string, data_no_null_terminator)
+{
+	struct string str;
+	constexpr const char data[] = {'t', 'e', 's', 't'};
+
+	ASSERT_EQ(string_init(&str, data, sizeof(data)),
+		  STRING_ERROR_INVALID_INPUT);
+}
+
+TEST(string, data_two_null_terminators)
+{
+	struct string str;
+	constexpr const char data[] = {'\0', 't', 'e', 's', 't', '\0'};
+
+	ASSERT_EQ(string_init(&str, data, sizeof(data)),
+		  STRING_ERROR_INVALID_INPUT);
+}
+
+} /* namespace */