Add smm-variable service level tests

Adds tests that use the public service access protocol to verify
client access to the smm-variable service. A C++ client is added
to facilitate testing. Service level tests may be run in a native
PC environment or from Linux user-space on a real target.

Signed-off-by: Julian Hall <julian.hall@arm.com>
Change-Id: I6f2ecdcb3517b1392d689b41eeb17bda7e58952c
diff --git a/components/service/smm_variable/client/cpp/component.cmake b/components/service/smm_variable/client/cpp/component.cmake
new file mode 100644
index 0000000..0a3155d
--- /dev/null
+++ b/components/service/smm_variable/client/cpp/component.cmake
@@ -0,0 +1,13 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2021, 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}/smm_variable_client.cpp"
+	)
diff --git a/components/service/smm_variable/client/cpp/smm_variable_client.cpp b/components/service/smm_variable/client/cpp/smm_variable_client.cpp
new file mode 100644
index 0000000..4d482c3
--- /dev/null
+++ b/components/service/smm_variable/client/cpp/smm_variable_client.cpp
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <cstring>
+#include <protocols/rpc/common/packed-c/status.h>
+#include <rpc_caller.h>
+#include "smm_variable_client.h"
+
+smm_variable_client::smm_variable_client() :
+	m_caller(NULL),
+	m_err_rpc_status(TS_RPC_CALL_ACCEPTED)
+{
+
+}
+
+smm_variable_client::smm_variable_client(
+	struct rpc_caller *caller) :
+	m_caller(caller),
+	m_err_rpc_status(TS_RPC_CALL_ACCEPTED)
+{
+
+}
+
+smm_variable_client::~smm_variable_client()
+{
+
+}
+
+void smm_variable_client::set_caller(struct rpc_caller *caller)
+{
+	m_caller = caller;
+}
+
+int smm_variable_client::err_rpc_status() const
+{
+	return m_err_rpc_status;
+}
+
+efi_status_t smm_variable_client::set_variable(
+	const EFI_GUID &guid,
+	const std::wstring &name,
+	const std::string &data,
+	uint32_t attributes)
+{
+	efi_status_t efi_status = EFI_NO_RESPONSE;
+
+	std::vector<int16_t> var_name = to_variable_name(name);
+	size_t name_size = var_name.size() * sizeof(int16_t);
+	size_t data_size = data.size();
+	size_t req_len = SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE_SIZE(name_size, data_size);
+
+	rpc_call_handle call_handle;
+	uint8_t *req_buf;
+
+	call_handle = rpc_caller_begin(m_caller, &req_buf, req_len);
+
+	if (call_handle) {
+
+		uint8_t *resp_buf;
+        size_t resp_len;
+		int opstatus;
+
+		SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE *access_var =
+			(SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE*)req_buf;
+
+		access_var->Guid = guid;
+		access_var->NameSize = name_size;
+		access_var->DataSize = data_size;
+		access_var->Attributes = attributes;
+
+		memcpy(access_var->Name, var_name.data(), name_size);
+		memcpy(&req_buf[SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE_DATA_OFFSET(access_var)],
+			data.data(), data_size);
+
+		m_err_rpc_status = rpc_caller_invoke(m_caller, call_handle,
+			SMM_VARIABLE_FUNCTION_SET_VARIABLE, &opstatus, &resp_buf, &resp_len);
+
+		if (m_err_rpc_status == TS_RPC_CALL_ACCEPTED) {
+
+			efi_status = opstatus;
+		}
+
+		rpc_caller_end(m_caller, call_handle);
+	}
+
+	return efi_status;
+}
+
+efi_status_t smm_variable_client::get_variable(
+	const EFI_GUID &guid,
+	const std::wstring &name,
+	std::string &data)
+{
+	efi_status_t efi_status = EFI_NO_RESPONSE;
+
+	std::vector<int16_t> var_name = to_variable_name(name);
+	size_t name_size = var_name.size() * sizeof(int16_t);
+	size_t data_size = 0;
+	size_t req_len = SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE_SIZE(name_size, data_size);
+
+	rpc_call_handle call_handle;
+	uint8_t *req_buf;
+
+	call_handle = rpc_caller_begin(m_caller, &req_buf, req_len);
+
+	if (call_handle) {
+
+		uint8_t *resp_buf;
+        size_t resp_len;
+		int opstatus;
+
+		SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE *access_var =
+			(SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE*)req_buf;
+
+		access_var->Guid = guid;
+		access_var->NameSize = name_size;
+		access_var->DataSize = data_size;
+
+		memcpy(access_var->Name, var_name.data(), name_size);
+
+		m_err_rpc_status = rpc_caller_invoke(m_caller, call_handle,
+			SMM_VARIABLE_FUNCTION_GET_VARIABLE, &opstatus, &resp_buf, &resp_len);
+
+		if (m_err_rpc_status == TS_RPC_CALL_ACCEPTED) {
+
+			efi_status = opstatus;
+
+			if (!efi_status) {
+
+				access_var = (SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE*)resp_buf;
+				data_size = access_var->DataSize;
+				const char *data_start = (const char*)
+					&resp_buf[SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE_DATA_OFFSET(access_var)];
+
+				data.assign(data_start, data_size);
+			}
+		}
+
+		rpc_caller_end(m_caller, call_handle);
+	}
+
+	return efi_status;
+}
+
+efi_status_t smm_variable_client::get_next_variable_name(
+	EFI_GUID &guid,
+	std::wstring &name)
+{
+	efi_status_t efi_status = EFI_NO_RESPONSE;
+
+	std::vector<int16_t> var_name = to_variable_name(name);
+	size_t name_size = var_name.size() * sizeof(int16_t);
+	size_t req_len = SMM_VARIABLE_COMMUNICATE_GET_NEXT_VARIABLE_NAME_SIZE(name_size);
+
+	rpc_call_handle call_handle;
+	uint8_t *req_buf;
+
+	call_handle = rpc_caller_begin(m_caller, &req_buf, req_len);
+
+	if (call_handle) {
+
+		uint8_t *resp_buf;
+        size_t resp_len;
+		int opstatus;
+
+		SMM_VARIABLE_COMMUNICATE_GET_NEXT_VARIABLE_NAME *next_var =
+			(SMM_VARIABLE_COMMUNICATE_GET_NEXT_VARIABLE_NAME*)req_buf;
+
+		next_var->Guid = guid;
+		next_var->NameSize = name_size;
+
+		memcpy(next_var->Name, var_name.data(), name_size);
+
+		m_err_rpc_status = rpc_caller_invoke(m_caller, call_handle,
+			SMM_VARIABLE_FUNCTION_GET_NEXT_VARIABLE_NAME, &opstatus, &resp_buf, &resp_len);
+
+		if (m_err_rpc_status == TS_RPC_CALL_ACCEPTED) {
+
+			efi_status = opstatus;
+
+			if (!efi_status) {
+
+				next_var = (SMM_VARIABLE_COMMUNICATE_GET_NEXT_VARIABLE_NAME*)resp_buf;
+				guid = next_var->Guid;
+				name = from_variable_name(next_var->Name, next_var->NameSize);
+			}
+		}
+
+		rpc_caller_end(m_caller, call_handle);
+	}
+
+	return efi_status;
+}
+
+efi_status_t smm_variable_client::exit_boot_service()
+{
+	efi_status_t efi_status = EFI_NO_RESPONSE;
+
+	size_t req_len = 0;
+	rpc_call_handle call_handle;
+	uint8_t *req_buf;
+
+	call_handle = rpc_caller_begin(m_caller, &req_buf, req_len);
+
+	if (call_handle) {
+
+		uint8_t *resp_buf;
+        size_t resp_len;
+		int opstatus;
+
+		m_err_rpc_status = rpc_caller_invoke(m_caller, call_handle,
+			SMM_VARIABLE_FUNCTION_EXIT_BOOT_SERVICE, &opstatus, &resp_buf, &resp_len);
+
+		if (m_err_rpc_status == TS_RPC_CALL_ACCEPTED) {
+
+			efi_status = opstatus;
+		}
+
+		rpc_caller_end(m_caller, call_handle);
+	}
+
+	return efi_status;
+}
+
+std::vector<int16_t> smm_variable_client::to_variable_name(
+	const std::wstring &string)
+{
+	std::vector<int16_t> var_name;
+
+	for (size_t i = 0; i < string.size(); i++) {
+
+		var_name.push_back((int16_t)string[i]);
+	}
+
+	var_name.push_back(0);
+
+	return var_name;
+}
+
+const std::wstring smm_variable_client::from_variable_name(
+	const int16_t *var_name,
+	size_t name_size)
+{
+	std::wstring name;
+	size_t num_chars = name_size / sizeof(int16_t);
+
+	for (size_t i = 0; i < num_chars; i++) {
+
+		if (!var_name[i])	break;  /* Reached null terminator */
+		name.push_back((wchar_t)var_name[i]);
+	}
+
+	return name;
+}
diff --git a/components/service/smm_variable/client/cpp/smm_variable_client.h b/components/service/smm_variable/client/cpp/smm_variable_client.h
new file mode 100644
index 0000000..3b9e172
--- /dev/null
+++ b/components/service/smm_variable/client/cpp/smm_variable_client.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef SMM_VARIABLE_CLIENT_H
+#define SMM_VARIABLE_CLIENT_H
+
+#include <cstdint>
+#include <string>
+#include <vector>
+#include <protocols/common/efi/efi_status.h>
+#include <protocols/service/smm_variable/smm_variable_proto.h>
+
+struct rpc_caller;
+
+/*
+ * Provides a C++ client interface for accessing an instance of the smm-variable service.
+ * This client is intented for testing the UEFI variable store provided by the smm-variable
+ * service.
+ */
+class smm_variable_client
+{
+public:
+
+	smm_variable_client();
+	smm_variable_client(struct rpc_caller *caller);
+	~smm_variable_client();
+
+	void set_caller(struct rpc_caller *caller);
+	int err_rpc_status() const;
+
+	/* Set a string type variable */
+	efi_status_t set_variable(
+		const EFI_GUID &guid,
+		const std::wstring &name,
+		const std::string &data,
+		uint32_t attributes);
+
+	/* Get a string type variable */
+	efi_status_t get_variable(
+		const EFI_GUID &guid,
+		const std::wstring &name,
+		std::string &data);
+
+	/* Get the next variable name - for enumerating store contents */
+	efi_status_t get_next_variable_name(
+		EFI_GUID &guid,
+		std::wstring &name);
+
+	/* Exit boot service */
+	efi_status_t exit_boot_service();
+
+private:
+	static std::vector<int16_t> to_variable_name(const std::wstring &string);
+	static const std::wstring from_variable_name(const int16_t *name, size_t name_size);
+
+	struct rpc_caller *m_caller;
+	int m_err_rpc_status;
+};
+
+#endif /* SMM_VARIABLE_CLIENT_H */
diff --git a/components/service/smm_variable/test/service/component.cmake b/components/service/smm_variable/test/service/component.cmake
new file mode 100644
index 0000000..7bfff96
--- /dev/null
+++ b/components/service/smm_variable/test/service/component.cmake
@@ -0,0 +1,13 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2021, 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}/smm_variable_service_tests.cpp"
+	)
diff --git a/components/service/smm_variable/test/service/smm_variable_service_tests.cpp b/components/service/smm_variable/test/service/smm_variable_service_tests.cpp
new file mode 100644
index 0000000..d542b16
--- /dev/null
+++ b/components/service/smm_variable/test/service/smm_variable_service_tests.cpp
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <service/smm_variable/client/cpp/smm_variable_client.h>
+#include <protocols/rpc/common/packed-c/encoding.h>
+#include <service_locator.h>
+#include <CppUTest/TestHarness.h>
+
+/*
+ * Service-level tests for the smm-variable service.
+ */
+TEST_GROUP(SmmVariableServiceTests)
+{
+	void setup()
+	{
+		struct rpc_caller *caller;
+		int status;
+
+		m_rpc_session_handle = NULL;
+		m_service_context = NULL;
+
+		service_locator_init();
+
+		m_service_context =
+			service_locator_query("sn:trustedfirmware.org:smm-variable:0", &status);
+		CHECK_TRUE(m_service_context);
+
+		m_rpc_session_handle =
+			service_context_open(m_service_context, TS_RPC_ENCODING_PACKED_C, &caller);
+		CHECK_TRUE(m_rpc_session_handle);
+
+		m_client = new smm_variable_client(caller);
+
+		setup_common_guid();
+	}
+
+	void teardown()
+	{
+		delete m_client;
+		m_client = NULL;
+
+		service_context_close(m_service_context, m_rpc_session_handle);
+		m_rpc_session_handle = NULL;
+
+		service_context_relinquish(m_service_context);
+		m_service_context = NULL;
+	}
+
+	void setup_common_guid()
+	{
+		m_common_guid.Data1 = 0x12341234;
+		m_common_guid.Data2 = 0x1234;
+		m_common_guid.Data3 = 0x1234;
+		m_common_guid.Data4[0] = 0x00;
+		m_common_guid.Data4[1] = 0x01;
+		m_common_guid.Data4[2] = 0x02;
+		m_common_guid.Data4[3] = 0x03;
+		m_common_guid.Data4[4] = 0x04;
+		m_common_guid.Data4[5] = 0x05;
+		m_common_guid.Data4[6] = 0x06;
+		m_common_guid.Data4[7] = 0x07;
+	}
+
+	smm_variable_client *m_client;
+	rpc_session_handle m_rpc_session_handle;
+	struct service_context *m_service_context;
+	EFI_GUID m_common_guid;
+};
+
+TEST(SmmVariableServiceTests, setAndGet)
+{
+	efi_status_t efi_status = EFI_SUCCESS;
+	std::wstring var_name = L"test_variable";
+	std::string set_data = "UEFI variable data string";
+	std::string get_data;
+
+	efi_status = m_client->set_variable(
+		m_common_guid,
+		var_name,
+		set_data,
+		0);
+
+	UNSIGNED_LONGS_EQUAL(EFI_SUCCESS, efi_status);
+
+	efi_status = m_client->get_variable(
+		m_common_guid,
+		var_name,
+		get_data);
+
+	UNSIGNED_LONGS_EQUAL(EFI_SUCCESS, efi_status);
+
+	UNSIGNED_LONGS_EQUAL(set_data.size(), get_data.size());
+	LONGS_EQUAL(0, get_data.compare(set_data));
+}
+
+TEST(SmmVariableServiceTests, setAndGetNv)
+{
+	efi_status_t efi_status = EFI_SUCCESS;
+	std::wstring var_name = L"an NV test_variable";
+	std::string set_data = "Another UEFI variable data string";
+	std::string get_data;
+
+	efi_status = m_client->set_variable(
+		m_common_guid,
+		var_name,
+		set_data,
+		EFI_VARIABLE_NON_VOLATILE);
+
+	UNSIGNED_LONGS_EQUAL(EFI_SUCCESS, efi_status);
+
+	efi_status = m_client->get_variable(
+		m_common_guid,
+		var_name,
+		get_data);
+
+	UNSIGNED_LONGS_EQUAL(EFI_SUCCESS, efi_status);
+
+	UNSIGNED_LONGS_EQUAL(set_data.size(), get_data.size());
+	LONGS_EQUAL(0, get_data.compare(set_data));
+}
+
+TEST(SmmVariableServiceTests, bootOnlyAccess)
+{
+	int efi_status = 0;
+	std::wstring var_name = L"a boot variable";
+	std::string set_data = "Only accessible during boot";
+	std::string get_data;
+
+	efi_status = m_client->set_variable(
+		m_common_guid,
+		var_name,
+		set_data,
+		EFI_VARIABLE_BOOTSERVICE_ACCESS);
+
+	LONGS_EQUAL(0, efi_status);
+
+	/* Expect access to be permitted */
+	efi_status = m_client->get_variable(
+		m_common_guid,
+		var_name,
+		get_data);
+
+	LONGS_EQUAL(0, efi_status);
+
+	UNSIGNED_LONGS_EQUAL(set_data.size(), get_data.size());
+	LONGS_EQUAL(0, get_data.compare(set_data));
+
+	/* Exit boot service - access should no longer be permitted */
+	efi_status = m_client->exit_boot_service();
+	LONGS_EQUAL(0, efi_status);
+
+	efi_status = m_client->get_variable(
+		m_common_guid,
+		var_name,
+		get_data);
+
+	CHECK_FALSE(!efi_status);
+}
+
+TEST(SmmVariableServiceTests, runtimeOnlyAccess)
+{
+	int efi_status = 0;
+	std::wstring var_name = L"a runtime variable";
+	std::string set_data = "Only accessible during runtime";
+	std::string get_data;
+
+	efi_status = m_client->set_variable(
+		m_common_guid,
+		var_name,
+		set_data,
+		EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_RUNTIME_ACCESS);
+
+	LONGS_EQUAL(0, efi_status);
+
+	/* Still booting so access should be forbidden */
+	efi_status = m_client->get_variable(
+		m_common_guid,
+		var_name,
+		get_data);
+
+	CHECK_FALSE(!efi_status);
+
+	/* Exit boot service - access should now be allowed */
+	efi_status = m_client->exit_boot_service();
+	LONGS_EQUAL(0, efi_status);
+
+	efi_status = m_client->get_variable(
+		m_common_guid,
+		var_name,
+		get_data);
+
+	LONGS_EQUAL(0, efi_status);
+	UNSIGNED_LONGS_EQUAL(set_data.size(), get_data.size());
+	LONGS_EQUAL(0, get_data.compare(set_data));
+}
+
+TEST(SmmVariableServiceTests, enumerateStoreContents)
+{
+	efi_status_t efi_status = EFI_SUCCESS;
+	std::wstring var_name_1 = L"varibale_1";
+	std::wstring var_name_2 = L"varibale_2";
+	std::wstring var_name_3 = L"varibale_3";
+	std::string set_data = "Some variable data";
+
+	/* Add some variables to the store */
+	efi_status = m_client->set_variable(
+		m_common_guid,
+		var_name_1,
+		set_data,
+		EFI_VARIABLE_NON_VOLATILE);
+
+	UNSIGNED_LONGS_EQUAL(EFI_SUCCESS, efi_status);
+
+	efi_status = m_client->set_variable(
+		m_common_guid,
+		var_name_2,
+		set_data,
+		EFI_VARIABLE_NON_VOLATILE);
+
+	UNSIGNED_LONGS_EQUAL(EFI_SUCCESS, efi_status);
+
+	efi_status = m_client->set_variable(
+		m_common_guid,
+		var_name_3,
+		set_data,
+		0);
+
+	UNSIGNED_LONGS_EQUAL(EFI_SUCCESS, efi_status);
+
+	/* Enumerate store contents - expect the values we added */
+	std::wstring var_name;
+	EFI_GUID guid = {0};
+
+	efi_status = m_client->get_next_variable_name(guid, var_name);
+	UNSIGNED_LONGS_EQUAL(EFI_SUCCESS, efi_status);
+	UNSIGNED_LONGS_EQUAL(var_name_1.size(), var_name.size());
+	LONGS_EQUAL(0, var_name.compare(var_name_1));
+
+	efi_status = m_client->get_next_variable_name(guid, var_name);
+	UNSIGNED_LONGS_EQUAL(EFI_SUCCESS, efi_status);
+	UNSIGNED_LONGS_EQUAL(var_name_2.size(), var_name.size());
+	LONGS_EQUAL(0, var_name.compare(var_name_2));
+
+	efi_status = m_client->get_next_variable_name(guid, var_name);
+	UNSIGNED_LONGS_EQUAL(EFI_SUCCESS, efi_status);
+	UNSIGNED_LONGS_EQUAL(var_name_3.size(), var_name.size());
+	LONGS_EQUAL(0, var_name.compare(var_name_3));
+
+	efi_status = m_client->get_next_variable_name(guid, var_name);
+	UNSIGNED_LONGS_EQUAL(EFI_NOT_FOUND, efi_status);
+}