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);
+}