Add test_runner service

This is a new service with client and provider that can be
used for running tests in a secure processing environment
and retrieving the results.  The test_runner provider allows
for arbitrary test farmework backends.  The goal is to
have a cpputest backend.  In this commit, a mock backend
is included for testing the service itself.  The service
has its own access protocol defined under the protocols
top-level directory.

Signed-off-by: Julian Hall <julian.hall@arm.com>
Change-Id: If4e965c110763bd805abbdcb87e7e03cd76248b2
diff --git a/components/app/remote-test-runner/component.cmake b/components/app/remote-test-runner/component.cmake
new file mode 100644
index 0000000..a47ec42
--- /dev/null
+++ b/components/app/remote-test-runner/component.cmake
@@ -0,0 +1,14 @@
+#-------------------------------------------------------------------------------
+# 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}/remote_test_runner.cpp"
+	)
+
diff --git a/components/app/remote-test-runner/remote_test_runner.cpp b/components/app/remote-test-runner/remote_test_runner.cpp
new file mode 100644
index 0000000..681c746
--- /dev/null
+++ b/components/app/remote-test-runner/remote_test_runner.cpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include "remote_test_runner.h"
+#include <protocols/service/test_runner/packed-c/status.h>
+#include <vector>
+#include <string>
+#include <cstring>
+#include <cstdio>
+
+remote_test_runner::remote_test_runner() :
+    m_client(NULL)
+{
+
+}
+
+remote_test_runner::remote_test_runner(test_runner_client *client) :
+    m_client(client)
+{
+
+}
+
+remote_test_runner::~remote_test_runner()
+{
+
+}
+
+void remote_test_runner::set_client(test_runner_client *client)
+{
+    m_client = client;
+}
+
+int remote_test_runner::execute(int argc, char *argv[])
+{
+    int test_status = TS_TEST_RUNNER_STATUS_ERROR;
+    struct test_spec spec;
+
+    /* Parse command line parameters */
+    bool list_only = option_selected("-l", argc, argv);
+    parse_test_spec_params(argc, argv, spec);
+
+    /* Run or list tests qualified bu spec */
+    struct test_summary summary;
+    std::vector<struct test_result> results;
+
+    if (list_only) {
+
+        test_status = m_client->list_tests(spec, summary, results);
+        output_list(summary, results);
+    }
+    else {
+
+        test_status = m_client->run_tests(spec, summary, results);
+        output_results(summary, results);
+    }
+
+    if (test_status != TS_TEST_RUNNER_STATUS_SUCCESS) {
+
+        printf("Tests failed to run with error: %d\n", test_status);
+    }
+
+    return test_status;
+}
+
+void remote_test_runner::parse_test_spec_params(int argc, char *argv[], struct test_spec &spec) const
+{
+    std::string name = parse_option("-n", argc, argv);
+    std::string group = parse_option("-g", argc, argv);
+
+    memset(spec.name, 0, TEST_NAME_MAX_LEN);
+    name.copy(spec.name, TEST_NAME_MAX_LEN - 1);
+
+    memset(spec.group, 0, TEST_GROUP_MAX_LEN);
+    group.copy(spec.group, TEST_GROUP_MAX_LEN - 1);
+}
+
+std::string remote_test_runner::parse_option(const char *option_switch, int argc, char *argv[]) const
+{
+    std::string option;
+
+    for (int i = 1; i + 1 < argc; ++i) {
+
+        if (strcmp(argv[i], option_switch) == 0) {
+
+            option = std::string(argv[i +1]);
+            break;
+        }
+    }
+
+    return option;
+}
+
+bool remote_test_runner::option_selected(const char *option_switch, int argc, char *argv[]) const
+{
+    bool selected = false;
+
+    for (int i = 1; (i < argc) && !selected; ++i) {
+
+        selected = (strcmp(argv[i], option_switch) == 0);
+    }
+
+    return selected;
+}
+
+void remote_test_runner::output_summary(const struct test_summary &summary)
+{
+    printf("\n");
+
+    if (summary.num_failed == 0)    printf("OK (");
+    else                            printf("Errors (%d failures, ", summary.num_failed);
+
+    printf("%d tests, %d ran)\n", summary.num_tests, summary.num_failed + summary.num_passed);
+}
+
+
+void remote_test_runner::output_list(const struct test_summary &summary,
+                                    const std::vector<struct test_result> &results)
+{
+
+}
+
+void remote_test_runner::output_results(const struct test_summary &summary,
+                                    const std::vector<struct test_result> &results)
+{
+    for (int i = 0; i < results.size(); ++i) {
+
+        printf("TEST(%s, %s) ", results[i].group, results[i].name);
+
+        if (results[i].run_state == TEST_RUN_STATE_PASSED) {
+
+            printf("OK\n");
+        }
+        else if (results[i].run_state == TEST_RUN_STATE_FAILED) {
+
+            printf("error\n");
+        }
+        else {
+
+            printf("did not run\n");
+        }
+    }
+
+    output_summary(summary);
+}
diff --git a/components/app/remote-test-runner/remote_test_runner.h b/components/app/remote-test-runner/remote_test_runner.h
new file mode 100644
index 0000000..9d1fca4
--- /dev/null
+++ b/components/app/remote-test-runner/remote_test_runner.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef REMOTE_TEST_RUNNER_H
+#define REMOTE_TEST_RUNNER_H
+
+#include <service/test_runner/client/cpp/test_runner_client.h>
+#include <service/test_runner/common/test_runner.h>
+
+/*
+ * Provides a command line interface for running remote tests.
+ */
+class remote_test_runner
+{
+public:
+    remote_test_runner();
+    remote_test_runner(test_runner_client *client);
+    virtual ~remote_test_runner();
+
+    void set_client(test_runner_client *client);
+
+    int execute(int argc, char *argv[]);
+
+private:
+
+    void parse_test_spec_params(int argc, char *argv[], struct test_spec &spec) const;
+    std::string parse_option(const char *option_switch, int argc, char *argv[]) const;
+    bool option_selected(const char *option_switch, int argc, char *argv[]) const;
+
+    void output_summary(const struct test_summary &summary);
+    void output_list(const struct test_summary &summary, const std::vector<struct test_result> &results);
+    void output_results(const struct test_summary &summary, const std::vector<struct test_result> &results);
+
+    test_runner_client *m_client;
+};
+
+#endif /* REMOTE_TEST_RUNNER_H */
diff --git a/components/common/tlv/test/tlv_tests.cpp b/components/common/tlv/test/tlv_tests.cpp
index 661a810..5d8a50d 100644
--- a/components/common/tlv/test/tlv_tests.cpp
+++ b/components/common/tlv/test/tlv_tests.cpp
@@ -76,6 +76,26 @@
     UNSIGNED_LONGS_EQUAL(1, decoded_record.length);
 }
 
+TEST(TlvTests, findAndDecodeMissingOptional)
+{
+    struct tlv_const_iterator iter;
+    struct tlv_record decoded_record;
+
+    /*
+     * Checks finding a missing record is correctly
+     * identified as not present but that the following
+     * record is found.
+     */
+    const uint8_t encoded_records[] = {
+        0x00, 0x07, 0x00, 0x02, 0x77, 0x77
+    };
+
+    tlv_const_iterator_begin(&iter, encoded_records, sizeof(encoded_records));
+    CHECK(!tlv_find_decode(&iter, 0x0001, &decoded_record));
+    CHECK(tlv_find_decode(&iter, 0x0007, &decoded_record));
+    CHECK_EQUAL(2, decoded_record.length);
+}
+
 TEST(TlvTests, decodeBadRecords)
 {
     struct tlv_const_iterator iter;
diff --git a/components/common/tlv/tlv.c b/components/common/tlv/tlv.c
index 1308832..80c1edb 100644
--- a/components/common/tlv/tlv.c
+++ b/components/common/tlv/tlv.c
@@ -82,10 +82,13 @@
 
 bool tlv_find_decode(struct tlv_const_iterator *iter, uint16_t tag, struct tlv_record *output)
 {
-    while (tlv_decode(iter, output)) {
+    struct tlv_const_iterator temp_iter = *iter;
+
+    while (tlv_decode(&temp_iter, output)) {
 
         if (output->tag == tag) {
-            /* Found a record  */
+            /* Found a record - update input iterator to next record */
+            *iter = temp_iter;
             return true;
         }
         else if (output->tag > tag) {
diff --git a/components/service/crypto/provider/serializer/packed-c/packedc_crypto_provider_serializer.c b/components/service/crypto/provider/serializer/packed-c/packedc_crypto_provider_serializer.c
index f39aa1c..078a88f 100644
--- a/components/service/crypto/provider/serializer/packed-c/packedc_crypto_provider_serializer.c
+++ b/components/service/crypto/provider/serializer/packed-c/packedc_crypto_provider_serializer.c
@@ -233,7 +233,7 @@
 
     if (expected_fixed_len <= req_buf->data_len) {
 
-        struct tlv_const_iterator resp_iter;
+        struct tlv_const_iterator req_iter;
         struct tlv_record decoded_record;
 
         rpc_status = TS_RPC_CALL_ACCEPTED;
@@ -241,11 +241,11 @@
         memcpy(&recv_msg, req_buf->data, expected_fixed_len);
         packedc_crypto_provider_translate_key_attributes(attributes, &recv_msg.attributes);
 
-        tlv_const_iterator_begin(&resp_iter,
+        tlv_const_iterator_begin(&req_iter,
             (uint8_t*)req_buf->data + expected_fixed_len,
             req_buf->data_len - expected_fixed_len);
 
-        if (tlv_find_decode(&resp_iter, TS_CRYPTO_IMPORT_KEY_IN_TAG_DATA, &decoded_record)) {
+        if (tlv_find_decode(&req_iter, TS_CRYPTO_IMPORT_KEY_IN_TAG_DATA, &decoded_record)) {
 
             if (decoded_record.length <= *data_len) {
 
@@ -296,7 +296,7 @@
 
     if (expected_fixed_len <= req_buf->data_len) {
 
-        struct tlv_const_iterator resp_iter;
+        struct tlv_const_iterator req_iter;
         struct tlv_record decoded_record;
 
         rpc_status = TS_RPC_CALL_ACCEPTED;
@@ -306,11 +306,11 @@
         *handle = recv_msg.handle;
         *alg = recv_msg.alg;
 
-        tlv_const_iterator_begin(&resp_iter,
+        tlv_const_iterator_begin(&req_iter,
             (uint8_t*)req_buf->data + expected_fixed_len,
             req_buf->data_len - expected_fixed_len);
 
-        if (tlv_find_decode(&resp_iter, TS_CRYPTO_SIGN_HASH_IN_TAG_HASH, &decoded_record)) {
+        if (tlv_find_decode(&req_iter, TS_CRYPTO_SIGN_HASH_IN_TAG_HASH, &decoded_record)) {
 
             if (decoded_record.length <= *hash_len) {
 
@@ -365,7 +365,7 @@
 
     if (expected_fixed_len <= req_buf->data_len) {
 
-        struct tlv_const_iterator resp_iter;
+        struct tlv_const_iterator req_iter;
         struct tlv_record decoded_record;
 
         rpc_status = TS_RPC_CALL_ACCEPTED;
@@ -375,11 +375,11 @@
         *handle = recv_msg.handle;
         *alg = recv_msg.alg;
 
-        tlv_const_iterator_begin(&resp_iter,
+        tlv_const_iterator_begin(&req_iter,
             (uint8_t*)req_buf->data + expected_fixed_len,
             req_buf->data_len - expected_fixed_len);
 
-        if (tlv_find_decode(&resp_iter, TS_CRYPTO_VERIFY_HASH_IN_TAG_HASH, &decoded_record)) {
+        if (tlv_find_decode(&req_iter, TS_CRYPTO_VERIFY_HASH_IN_TAG_HASH, &decoded_record)) {
 
             if (decoded_record.length <= *hash_len) {
 
@@ -396,7 +396,7 @@
             *hash_len = 0;
         }
 
-        if (tlv_find_decode(&resp_iter, TS_CRYPTO_VERIFY_HASH_IN_TAG_SIGNATURE, &decoded_record)) {
+        if (tlv_find_decode(&req_iter, TS_CRYPTO_VERIFY_HASH_IN_TAG_SIGNATURE, &decoded_record)) {
 
             if (decoded_record.length <= *sig_len) {
 
@@ -429,7 +429,7 @@
 
     if (expected_fixed_len <= req_buf->data_len) {
 
-        struct tlv_const_iterator resp_iter;
+        struct tlv_const_iterator req_iter;
         struct tlv_record decoded_record;
 
         rpc_status = TS_RPC_CALL_ACCEPTED;
@@ -439,11 +439,11 @@
         *handle = recv_msg.handle;
         *alg = recv_msg.alg;
 
-        tlv_const_iterator_begin(&resp_iter,
+        tlv_const_iterator_begin(&req_iter,
             (uint8_t*)req_buf->data + expected_fixed_len,
             req_buf->data_len - expected_fixed_len);
 
-        if (tlv_find_decode(&resp_iter, TS_CRYPTO_ASYMMETRIC_DECRYPT_IN_TAG_CIPHERTEXT, &decoded_record)) {
+        if (tlv_find_decode(&req_iter, TS_CRYPTO_ASYMMETRIC_DECRYPT_IN_TAG_CIPHERTEXT, &decoded_record)) {
 
             if (decoded_record.length <= *ciphertext_len) {
 
@@ -460,7 +460,7 @@
             *ciphertext_len = 0;
         }
 
-        if (tlv_find_decode(&resp_iter, TS_CRYPTO_ASYMMETRIC_DECRYPT_IN_TAG_SALT, &decoded_record)) {
+        if (tlv_find_decode(&req_iter, TS_CRYPTO_ASYMMETRIC_DECRYPT_IN_TAG_SALT, &decoded_record)) {
 
             if (decoded_record.length <= *salt_len) {
 
@@ -515,7 +515,7 @@
 
     if (expected_fixed_len <= req_buf->data_len) {
 
-        struct tlv_const_iterator resp_iter;
+        struct tlv_const_iterator req_iter;
         struct tlv_record decoded_record;
 
         rpc_status = TS_RPC_CALL_ACCEPTED;
@@ -525,11 +525,11 @@
         *handle = recv_msg.handle;
         *alg = recv_msg.alg;
 
-        tlv_const_iterator_begin(&resp_iter,
+        tlv_const_iterator_begin(&req_iter,
             (uint8_t*)req_buf->data + expected_fixed_len,
             req_buf->data_len - expected_fixed_len);
 
-        if (tlv_find_decode(&resp_iter, TS_CRYPTO_ASYMMETRIC_ENCRYPT_IN_TAG_PLAINTEXT, &decoded_record)) {
+        if (tlv_find_decode(&req_iter, TS_CRYPTO_ASYMMETRIC_ENCRYPT_IN_TAG_PLAINTEXT, &decoded_record)) {
 
             if (decoded_record.length <= *plaintext_len) {
 
@@ -546,7 +546,7 @@
             *plaintext_len = 0;
         }
 
-        if (tlv_find_decode(&resp_iter, TS_CRYPTO_ASYMMETRIC_ENCRYPT_IN_TAG_SALT, &decoded_record)) {
+        if (tlv_find_decode(&req_iter, TS_CRYPTO_ASYMMETRIC_ENCRYPT_IN_TAG_SALT, &decoded_record)) {
 
             if (decoded_record.length <= *salt_len) {
 
diff --git a/components/service/locator/linux/ffa/linuxffa_location_strategy.c b/components/service/locator/linux/ffa/linuxffa_location_strategy.c
index e965076..f7e78ad 100644
--- a/components/service/locator/linux/ffa/linuxffa_location_strategy.c
+++ b/components/service/locator/linux/ffa/linuxffa_location_strategy.c
@@ -90,6 +90,7 @@
     {
         {"crypto",              "d9df52d5-16a2-4bb2-9aa4-d26d3b84e8c0"},
         {"secure-storage",      "dc1eef48-b17a-4ccf-ac8b-dfcff7711b14"},
+        {"test-runner",         "33c75baf-ac6a-4fe4-8ac7-e9909bee2d17"},
         {NULL,                  NULL}
     };
 
diff --git a/components/service/locator/standalone/services/test-runner/component.cmake b/components/service/locator/standalone/services/test-runner/component.cmake
new file mode 100644
index 0000000..e30f2c6
--- /dev/null
+++ b/components/service/locator/standalone/services/test-runner/component.cmake
@@ -0,0 +1,14 @@
+#-------------------------------------------------------------------------------
+# 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}/test_runner_service_context.cpp"
+	)
+
diff --git a/components/service/locator/standalone/services/test-runner/test_runner_service_context.cpp b/components/service/locator/standalone/services/test-runner/test_runner_service_context.cpp
new file mode 100644
index 0000000..103f188
--- /dev/null
+++ b/components/service/locator/standalone/services/test-runner/test_runner_service_context.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2020-2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include "test_runner_service_context.h"
+#include <service/test_runner/provider/serializer/packed-c/packedc_test_runner_provider_serializer.h>
+
+test_runner_service_context::test_runner_service_context(const char *sn) :
+    standalone_service_context(sn),
+    m_test_runner_provider()
+{
+
+}
+
+test_runner_service_context::~test_runner_service_context()
+{
+
+}
+
+void test_runner_service_context::do_init()
+{
+    struct rpc_interface *test_runner_ep = test_runner_provider_init(&m_test_runner_provider);
+
+    test_runner_provider_register_serializer(&m_test_runner_provider,
+                    TS_RPC_ENCODING_PACKED_C, packedc_test_runner_provider_serializer_instance());
+
+    standalone_service_context::set_rpc_interface(test_runner_ep);
+}
+
+void test_runner_service_context::do_deinit()
+{
+    test_runner_provider_deinit(&m_test_runner_provider);
+}
diff --git a/components/service/locator/standalone/services/test-runner/test_runner_service_context.h b/components/service/locator/standalone/services/test-runner/test_runner_service_context.h
new file mode 100644
index 0000000..8474ec2
--- /dev/null
+++ b/components/service/locator/standalone/services/test-runner/test_runner_service_context.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef STANDALONE_TEST_RUNNER_SERVICE_CONTEXT_H
+#define STANDALONE_TEST_RUNNER_SERVICE_CONTEXT_H
+
+#include <service/locator/standalone/standalone_service_context.h>
+#include <rpc/direct/direct_caller.h>
+#include <service/test_runner/provider/test_runner_provider.h>
+
+class test_runner_service_context : public standalone_service_context
+{
+public:
+    test_runner_service_context(const char *sn);
+    virtual ~test_runner_service_context();
+
+private:
+
+    void do_init();
+    void do_deinit();
+
+    struct test_runner_provider m_test_runner_provider;
+};
+
+#endif /* STANDALONE_TEST_RUNNER_SERVICE_CONTEXT_H */
diff --git a/components/service/locator/standalone/standalone_env.cpp b/components/service/locator/standalone/standalone_env.cpp
index 80d1777..41dd206 100644
--- a/components/service/locator/standalone/standalone_env.cpp
+++ b/components/service/locator/standalone/standalone_env.cpp
@@ -1,11 +1,12 @@
 /*
- * Copyright (c) 2020, Arm Limited and Contributors. All rights reserved.
+ * Copyright (c) 2020-2021, Arm Limited and Contributors. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
 
 #include <service_locator.h>
 #include <service/locator/standalone/services/crypto/crypto_service_context.h>
+#include <service/locator/standalone/services/test-runner/test_runner_service_context.h>
 #include "standalone_location_strategy.h"
 #include "standalone_service_registry.h"
 
@@ -13,5 +14,9 @@
 {
     static crypto_service_context crypto_context("sn:trustedfirmware.org:crypto:0");
     standalone_service_registry::instance()->regsiter_service_instance(&crypto_context);
+
+    static test_runner_service_context test_runner_context("sn:trustedfirmware.org:test-runner:0");
+    standalone_service_registry::instance()->regsiter_service_instance(&test_runner_context);
+
     service_locator_register_strategy(standalone_location_strategy());
 }
\ No newline at end of file
diff --git a/components/service/test_runner/client/cpp/component.cmake b/components/service/test_runner/client/cpp/component.cmake
new file mode 100644
index 0000000..8bd9e51
--- /dev/null
+++ b/components/service/test_runner/client/cpp/component.cmake
@@ -0,0 +1,14 @@
+#-------------------------------------------------------------------------------
+# 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}/test_runner_client.cpp"
+	)
+
diff --git a/components/service/test_runner/client/cpp/test_runner_client.cpp b/components/service/test_runner/client/cpp/test_runner_client.cpp
new file mode 100644
index 0000000..a780002
--- /dev/null
+++ b/components/service/test_runner/client/cpp/test_runner_client.cpp
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include "test_runner_client.h"
+#include <protocols/rpc/common/packed-c/status.h>
+#include <protocols/service/test_runner/packed-c/opcodes.h>
+#include <protocols/service/test_runner/packed-c/status.h>
+#include <protocols/service/test_runner/packed-c/run_tests.h>
+#include <protocols/service/test_runner/packed-c/list_tests.h>
+#include <rpc_caller.h>
+#include <common/tlv/tlv.h>
+#include <cstddef>
+#include <cstring>
+#include <string>
+
+test_runner_client::test_runner_client() :
+    m_caller(NULL),
+    m_err_rpc_status(TS_RPC_CALL_ACCEPTED)
+{
+
+}
+
+test_runner_client::test_runner_client(struct rpc_caller *caller) :
+    m_caller(caller),
+    m_err_rpc_status(TS_RPC_CALL_ACCEPTED)
+{
+
+}
+
+test_runner_client::~test_runner_client()
+{
+
+}
+
+void test_runner_client::set_caller(struct rpc_caller *caller)
+{
+    m_caller = caller;
+}
+
+int test_runner_client::err_rpc_status() const
+{
+    return m_err_rpc_status;
+}
+
+int test_runner_client::run_tests(
+    const struct test_spec &spec,
+    struct test_summary &summary,
+    std::vector<struct test_result> &results)
+{
+    return iterate_over_tests(spec, false, summary, results);
+}
+
+int test_runner_client::list_tests(
+    const struct test_spec &spec,
+    struct test_summary &summary,
+    std::vector<struct test_result> &results)
+{
+    return iterate_over_tests(spec, true, summary, results);
+}
+
+int test_runner_client::iterate_over_tests(
+    const struct test_spec &spec, bool list_only,
+    struct test_summary &summary,
+    std::vector<struct test_result> &results)
+{
+    int test_status = TS_TEST_RUNNER_STATUS_ERROR;
+    m_err_rpc_status = TS_RPC_ERROR_RESOURCE_FAILURE;
+    rpc_call_handle call_handle;
+    uint8_t *req_buf;
+    std::vector<uint8_t> req_param;
+
+    serialize_test_spec(req_param, spec);
+
+    size_t req_len = req_param.size();
+    call_handle = rpc_caller_begin(m_caller, &req_buf, req_len);
+
+    if (call_handle) {
+
+        uint8_t *resp_buf;
+        size_t resp_len;
+        int opstatus;
+
+        memcpy(req_buf, req_param.data(), req_len);
+
+        uint32_t opcode = (list_only) ?
+            TS_TEST_RUNNER_OPCODE_LIST_TESTS :
+            TS_TEST_RUNNER_OPCODE_RUN_TESTS;
+
+        m_err_rpc_status = rpc_caller_invoke(m_caller, call_handle,
+            opcode, &opstatus, &resp_buf, &resp_len);
+
+        if (m_err_rpc_status == TS_RPC_CALL_ACCEPTED) {
+
+            test_status = opstatus;
+
+            if (opstatus == TS_TEST_RUNNER_STATUS_SUCCESS) {
+
+                test_status = deserialize_results(resp_buf, resp_len, summary, results);
+            }
+        }
+
+        rpc_caller_end(m_caller, call_handle);
+    }
+
+    return test_status;
+}
+
+void test_runner_client::serialize_test_spec(
+    std::vector<uint8_t> &serialized_data,
+    const struct test_spec &spec) const
+{
+    size_t name_len = strlen(spec.name);
+    size_t group_len = strlen(spec.group);
+    size_t tlv_space = 0;
+
+    /* First determine buffer space needed for TLV parameters */
+    if (name_len)  tlv_space += tlv_required_space(name_len);
+    if (group_len) tlv_space += tlv_required_space(group_len);
+
+    /* Extend the params vector and write the tlv records */
+    if (tlv_space) {
+
+        serialized_data.resize(tlv_space);
+        struct tlv_iterator iter;
+        uint8_t *buf = serialized_data.data();
+
+        tlv_iterator_begin(&iter, buf, tlv_space);
+
+        if (name_len) {
+
+            struct tlv_record record;
+            record.tag = TS_TEST_RUNNER_TEST_SPEC_TAG_NAME;
+            record.length = name_len;
+            record.value = (const uint8_t*)spec.name;
+            tlv_encode(&iter, &record);
+        }
+
+        if (group_len) {
+
+            struct tlv_record record;
+            record.tag = TS_TEST_RUNNER_TEST_SPEC_TAG_GROUP;
+            record.length = group_len;
+            record.value = (const uint8_t*)spec.group;
+            tlv_encode(&iter, &record);
+        }
+    }
+}
+
+int test_runner_client::deserialize_results(
+    const uint8_t *resp_buf, size_t resp_len,
+    struct test_summary &summary,
+    std::vector<struct test_result> &results) const
+{
+    int test_status = TS_TEST_RUNNER_STATUS_SUCCESS;
+    size_t fixed_size = sizeof(ts_test_runner_result_summary);
+
+    if (resp_len >= fixed_size) {
+
+        /* Deserialize fixed size summary structure */
+        struct ts_test_runner_result_summary packed_summary;
+        memcpy(&packed_summary, resp_buf, fixed_size);
+
+        summary.num_tests = packed_summary.num_tests;
+        summary.num_passed = packed_summary.num_passed;
+        summary.num_failed = packed_summary.num_failed;
+        summary.num_results = 0;
+
+        /* Deserialize any test result records */
+        struct tlv_const_iterator tlv_iter;
+        struct tlv_record result_record;
+        tlv_const_iterator_begin(&tlv_iter, &resp_buf[fixed_size], resp_len - fixed_size);
+
+        while (tlv_find_decode(&tlv_iter, TS_TEST_RUNNER_TEST_RESULT_TAG, &result_record)) {
+
+            struct test_result result;
+
+            test_status = deserialize_result(result_record.value, result_record.length, result);
+
+            if (test_status == TS_TEST_RUNNER_STATUS_SUCCESS) {
+
+                results.push_back(result);
+                ++summary.num_results;
+            }
+            else {
+                /* Failed to decode result record */
+                break;
+            }
+        }
+    }
+    else {
+        /* Failed to mandatory test summary */
+        test_status = TS_TEST_RUNNER_STATUS_INVALID_TEST_RESULTS;
+    }
+
+    return test_status;
+}
+
+int test_runner_client::deserialize_result(
+    const uint8_t *value_buf, size_t value_len,
+    struct test_result &result) const
+{
+    int test_status = TS_TEST_RUNNER_STATUS_SUCCESS;
+    size_t fixed_size = sizeof(ts_test_runner_test_result);
+
+    if (value_len >= fixed_size) {
+
+        struct ts_test_runner_test_result packed_result;
+        memcpy(&packed_result, value_buf, fixed_size);
+
+        result.run_state = (enum test_run_state)packed_result.run_state;
+        result.fail_line = packed_result.fail_line;
+        result.name[0] = 0;
+        result.group[0] = 0;
+
+        /* Deserialize name and group if present */
+        struct tlv_const_iterator req_iter;
+        struct tlv_record decoded_record;
+
+        tlv_const_iterator_begin(&req_iter, &value_buf[fixed_size], value_len - fixed_size);
+
+        if (tlv_find_decode(&req_iter, TS_TEST_RUNNER_TEST_RESULT_TAG_NAME, &decoded_record)) {
+
+            if ((decoded_record.length > 0) && (decoded_record.length < TEST_NAME_MAX_LEN)) {
+
+                memcpy(result.name, decoded_record.value, decoded_record.length);
+                result.name[decoded_record.length] = 0;
+            }
+        }
+
+        if (tlv_find_decode(&req_iter, TS_TEST_RUNNER_TEST_RESULT_TAG_GROUP, &decoded_record)) {
+
+            if ((decoded_record.length > 0) && (decoded_record.length < TEST_GROUP_MAX_LEN)) {
+
+                memcpy(result.group, decoded_record.value, decoded_record.length);
+                result.group[decoded_record.length] = 0;
+            }
+        }
+    }
+    else {
+        /* Invalid test result */
+        test_status = TS_TEST_RUNNER_STATUS_INVALID_TEST_RESULTS;
+    }
+
+    return test_status;
+}
diff --git a/components/service/test_runner/client/cpp/test_runner_client.h b/components/service/test_runner/client/cpp/test_runner_client.h
new file mode 100644
index 0000000..b86b93e
--- /dev/null
+++ b/components/service/test_runner/client/cpp/test_runner_client.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef TEST_RUNNER_CLIENT_H
+#define TEST_RUNNER_CLIENT_H
+
+#include <cstdint>
+#include <vector>
+#include <string>
+#include <service/test_runner/common/test_runner.h>
+
+struct rpc_caller;
+
+/*
+ * Provides a client interface for running remote tests using the test-runner
+ * service access protocol.
+ */
+class test_runner_client
+{
+public:
+    test_runner_client();
+    test_runner_client(struct rpc_caller *caller);
+    virtual ~test_runner_client();
+
+    void set_caller(struct rpc_caller *caller);
+    int err_rpc_status() const;
+
+    int run_tests(const struct test_spec &spec,
+                struct test_summary &summary,
+                std::vector<struct test_result> &results);
+
+    int list_tests(const struct test_spec &spec,
+                struct test_summary &summary,
+                std::vector<struct test_result> &results);
+
+private:
+
+    int iterate_over_tests(const struct test_spec &spec, bool list_only,
+                struct test_summary &summary,
+                std::vector<struct test_result> &results);
+
+    void serialize_test_spec(std::vector<uint8_t> &serialized_data,
+                const struct test_spec &spec) const;
+
+    int deserialize_results(const uint8_t *resp_buf, size_t resp_len,
+                struct test_summary &summary,
+                std::vector<struct test_result> &results) const;
+
+    int deserialize_result(const uint8_t *value_buf, size_t value_len,
+                struct test_result &result) const;
+
+    struct rpc_caller *m_caller;
+    int m_err_rpc_status;
+};
+
+#endif /* TEST_RUNNER_CLIENT_H */
diff --git a/components/service/test_runner/common/test_runner.h b/components/service/test_runner/common/test_runner.h
new file mode 100644
index 0000000..c891918
--- /dev/null
+++ b/components/service/test_runner/common/test_runner.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2020-2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef CPPUTEST_TEST_RUNNER_H
+#define CPPUTEST_TEST_RUNNER_H
+
+#include <protocols/service/test_runner/packed-c/test_result.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Constants
+ */
+#define TEST_NAME_MAX_LEN		(30)
+#define TEST_GROUP_MAX_LEN		(30)
+
+/**
+ * Specifies a set of tests for running or listing.
+ */
+struct test_spec
+{
+	char name[TEST_NAME_MAX_LEN];
+	char group[TEST_GROUP_MAX_LEN];
+};
+
+/**
+ * A summary of a set of tests qualified by a test_spec.
+ */
+struct test_summary
+{
+	int num_tests;		/* Number of qualifying tests */
+	int num_results;	/* Number of available test result objects */
+	int num_passed;		/* Number that ran and passed */
+	int num_failed;		/* Number that ran and failed */
+};
+
+/**
+ * The run state of a test case
+ */
+enum test_run_state
+{
+	TEST_RUN_STATE_NOT_RUN = TS_TEST_RUNNER_TEST_RESULT_RUN_STATE_NOT_RUN,
+	TEST_RUN_STATE_PASSED = TS_TEST_RUNNER_TEST_RESULT_RUN_STATE_PASSED,
+	TEST_RUN_STATE_FAILED = TS_TEST_RUNNER_TEST_RESULT_RUN_STATE_FAILED
+};
+
+/**
+ * The result for a particular test case.
+ */
+struct test_result
+{
+	char name[TEST_NAME_MAX_LEN];
+	char group[TEST_GROUP_MAX_LEN];
+	enum test_run_state run_state;
+	unsigned int fail_line;
+};
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* CPPUTEST_TEST_RUNNER_H */
diff --git a/components/service/test_runner/provider/backend/mock/component.cmake b/components/service/test_runner/provider/backend/mock/component.cmake
new file mode 100644
index 0000000..4666b2b
--- /dev/null
+++ b/components/service/test_runner/provider/backend/mock/component.cmake
@@ -0,0 +1,14 @@
+#-------------------------------------------------------------------------------
+# 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}/mock_test_runner.c"
+	)
+
diff --git a/components/service/test_runner/provider/backend/mock/mock_test_runner.c b/components/service/test_runner/provider/backend/mock/mock_test_runner.c
new file mode 100644
index 0000000..9ea5936
--- /dev/null
+++ b/components/service/test_runner/provider/backend/mock/mock_test_runner.c
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include <service/test_runner/provider/test_runner_backend.h>
+#include <service/test_runner/provider/test_runner_provider.h>
+#include <stdbool.h>
+#include <string.h>
+
+/**
+ * The mock backend is a test_runner that provides some mock test cases
+ * that can be used for testing the test_runner service iteslf.
+ */
+
+struct mock_test_case
+{
+    const char *group;
+    const char *name;
+    bool (*test_func)(void);
+};
+
+/* Mock test test functions */
+static bool test_that_passes(void)  { return true; }
+static bool test_that_fails(void)   { return false; }
+
+/* Mock test suite */
+const struct mock_test_case mock_test_suite[] =
+{
+    {.group = "PlatformTests", .name = "Trng", .test_func = test_that_passes},
+    {.group = "PlatformTests", .name = "CheckIOmap", .test_func = test_that_passes},
+    {.group = "ConfigTests", .name = "ValidateConfig", .test_func = test_that_fails},
+    {.group = "ConfigTests", .name = "ApplyConfig", .test_func = test_that_passes}
+};
+
+
+static bool does_qualify(const struct mock_test_case *test_case, const struct test_spec *spec)
+{
+    return
+        ((strlen(spec->group) == 0) || (strcmp(spec->group, test_case->group) == 0)) &&
+        ((strlen(spec->name) == 0) || (strcmp(spec->name, test_case->name) == 0));
+}
+
+static size_t count_tests(const struct test_spec *spec)
+{
+    size_t count = 0;
+
+    for (size_t i = 0; i < sizeof(mock_test_suite)/sizeof(struct mock_test_case); ++i) {
+
+        if (does_qualify(&mock_test_suite[i], spec)) ++count;
+    }
+
+    return count;
+}
+
+static int run_tests(const struct test_spec *spec, struct test_summary *summary,
+                    struct test_result *results, size_t result_limit)
+{
+    summary->num_tests = 0;
+	summary->num_results = 0;
+	summary->num_passed = 0;
+	summary->num_failed = 0;
+
+    for (size_t i = 0; i < sizeof(mock_test_suite)/sizeof(struct mock_test_case); ++i) {
+
+        if (does_qualify(&mock_test_suite[i], spec)) {
+
+            bool did_pass = mock_test_suite[i].test_func();
+
+            if (did_pass)
+                ++summary->num_passed;
+            else
+                ++summary->num_failed;
+
+            if (summary->num_tests < result_limit) {
+
+                struct test_result *new_result = &results[summary->num_results];
+
+                new_result->run_state = (did_pass) ? TEST_RUN_STATE_PASSED : TEST_RUN_STATE_FAILED;
+                new_result->fail_line = 0;
+                strcpy(new_result->group, mock_test_suite[i].group);
+                strcpy(new_result->name, mock_test_suite[i].name);
+
+                ++summary->num_results;
+            }
+
+            ++summary->num_tests;
+        }
+    }
+
+    return 0;
+}
+
+static void list_tests(const struct test_spec *spec, struct test_summary *summary,
+                    struct test_result *results, size_t result_limit)
+{
+    summary->num_tests = 0;
+	summary->num_results = 0;
+	summary->num_passed = 0;
+	summary->num_failed = 0;
+
+    for (size_t i = 0; i < sizeof(mock_test_suite)/sizeof(struct mock_test_case); ++i) {
+
+        if (does_qualify(&mock_test_suite[i], spec)) {
+
+            if (summary->num_tests < result_limit) {
+
+                struct test_result *new_result = &results[summary->num_results];
+
+                new_result->run_state = TEST_RUN_STATE_NOT_RUN;
+                new_result->fail_line = 0;
+                strcpy(new_result->group, mock_test_suite[i].group);
+                strcpy(new_result->name, mock_test_suite[i].name);
+
+                ++summary->num_results;
+            }
+
+            ++summary->num_tests;
+        }
+    }
+}
+
+void test_runner_register_default_backend(struct test_runner_provider *context)
+{
+    static struct test_runner_backend this_instance;
+
+    this_instance.count_tests = count_tests;
+    this_instance.run_tests = run_tests;
+    this_instance.list_tests = list_tests;
+    this_instance.next = NULL;
+
+    test_runner_provider_register_backend(context, &this_instance);
+}
\ No newline at end of file
diff --git a/components/service/test_runner/provider/backend/null/component.cmake b/components/service/test_runner/provider/backend/null/component.cmake
new file mode 100644
index 0000000..8197412
--- /dev/null
+++ b/components/service/test_runner/provider/backend/null/component.cmake
@@ -0,0 +1,14 @@
+#-------------------------------------------------------------------------------
+# 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}/null_test_runner.c"
+	)
+
diff --git a/components/service/test_runner/provider/backend/null/null_test_runner.c b/components/service/test_runner/provider/backend/null/null_test_runner.c
new file mode 100644
index 0000000..9d4afb5
--- /dev/null
+++ b/components/service/test_runner/provider/backend/null/null_test_runner.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include <service/test_runner/provider/test_runner_backend.h>
+
+/**
+ * The null backend is a non-existent test_runner.  An implementation of
+ * the test_runner_register_default_backend() function is provided but it does
+ * nothing.  This component should be used in deployments where there is
+ * no need for a default test_runner.
+ */
+void test_runner_register_default_backend(struct test_runner_provider *context)
+{
+    /* Don't register anything */
+    (void)context;
+}
\ No newline at end of file
diff --git a/components/service/test_runner/provider/component.cmake b/components/service/test_runner/provider/component.cmake
new file mode 100644
index 0000000..4a37648
--- /dev/null
+++ b/components/service/test_runner/provider/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}/test_runner_provider.c"
+	)
diff --git a/components/service/test_runner/provider/serializer/packed-c/component.cmake b/components/service/test_runner/provider/serializer/packed-c/component.cmake
new file mode 100644
index 0000000..1180035
--- /dev/null
+++ b/components/service/test_runner/provider/serializer/packed-c/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}/packedc_test_runner_provider_serializer.c"
+	)
diff --git a/components/service/test_runner/provider/serializer/packed-c/packedc_test_runner_provider_serializer.c b/components/service/test_runner/provider/serializer/packed-c/packedc_test_runner_provider_serializer.c
new file mode 100644
index 0000000..577bfdc
--- /dev/null
+++ b/components/service/test_runner/provider/serializer/packed-c/packedc_test_runner_provider_serializer.c
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <common/tlv/tlv.h>
+#include <protocols/rpc/common/packed-c/status.h>
+#include <protocols/service/test_runner/packed-c/run_tests.h>
+#include <protocols/service/test_runner/packed-c/list_tests.h>
+#include "packedc_test_runner_provider_serializer.h"
+
+/* Common rerialization methods used for different operations */
+static rpc_status_t deserialize_test_spec(const struct call_param_buf *req_buf,
+        struct test_spec *test_spec)
+{
+    struct tlv_const_iterator req_iter;
+    struct tlv_record decoded_record;
+
+    test_spec->name[0] = 0;
+    test_spec->group[0] = 0;
+
+    tlv_const_iterator_begin(&req_iter, (uint8_t*)req_buf->data, req_buf->data_len);
+
+    if (tlv_find_decode(&req_iter, TS_TEST_RUNNER_TEST_SPEC_TAG_NAME, &decoded_record)) {
+
+        if ((decoded_record.length > 0) && (decoded_record.length < TEST_NAME_MAX_LEN)) {
+
+            memcpy(test_spec->name, decoded_record.value, decoded_record.length);
+            test_spec->name[decoded_record.length] = 0;
+        }
+    }
+
+    if (tlv_find_decode(&req_iter, TS_TEST_RUNNER_TEST_SPEC_TAG_GROUP, &decoded_record)) {
+
+        if ((decoded_record.length > 0) && (decoded_record.length < TEST_GROUP_MAX_LEN)) {
+
+            memcpy(test_spec->group, decoded_record.value, decoded_record.length);
+            test_spec->group[decoded_record.length] = 0;
+        }
+    }
+
+    return TS_RPC_CALL_ACCEPTED;
+}
+
+static uint8_t *serialize_test_result(const struct test_result *result, size_t *serialized_len)
+{
+    uint8_t *out_buf;
+    size_t fixed_len = sizeof(struct ts_test_runner_test_result);
+    size_t required_space = fixed_len;
+    size_t name_len = strlen(result->name);
+    size_t group_len = strlen(result->group);
+
+    if (name_len)   required_space += tlv_required_space(name_len);
+    if (group_len)  required_space += tlv_required_space(group_len);
+
+    *serialized_len = required_space;
+
+    out_buf = malloc(required_space);
+
+    if (out_buf) {
+
+        struct ts_test_runner_test_result result_msg;
+        result_msg.run_state = result->run_state;
+        result_msg.fail_line = result->fail_line;
+
+        memcpy(out_buf, &result_msg, fixed_len);
+
+        struct tlv_iterator tlv_iter;
+        tlv_iterator_begin(&tlv_iter, (uint8_t*)out_buf + fixed_len, required_space - fixed_len);
+
+        if (name_len) {
+
+            struct tlv_record record;
+            record.tag = TS_TEST_RUNNER_TEST_RESULT_TAG_NAME;
+            record.length = name_len;
+            record.value = result->name;
+            tlv_encode(&tlv_iter, &record);
+        }
+
+        if (group_len) {
+
+            struct tlv_record record;
+            record.tag = TS_TEST_RUNNER_TEST_RESULT_TAG_GROUP;
+            record.length = group_len;
+            record.value = result->group;
+            tlv_encode(&tlv_iter, &record);
+        }
+    }
+
+    return out_buf;
+}
+
+static rpc_status_t serialize_test_results(struct call_param_buf *resp_buf,
+        const struct test_summary *summary,
+        const struct test_result *results)
+{
+    size_t space_used = 0;
+    rpc_status_t rpc_status = TS_RPC_CALL_ACCEPTED;
+
+    /* Serialize fixed size summary */
+    struct  ts_test_runner_result_summary summary_msg;
+    size_t fixed_len = sizeof(struct  ts_test_runner_result_summary);
+
+    summary_msg.num_tests = summary->num_tests;
+    summary_msg.num_passed = summary->num_passed;
+    summary_msg.num_failed = summary->num_failed;
+
+    if (fixed_len + space_used <= resp_buf->size) {
+
+        memcpy((uint8_t*)resp_buf->data + space_used, &summary_msg, fixed_len);
+        space_used += fixed_len;
+
+        /* Serialize test result objects */
+        struct tlv_iterator resp_iter;
+        tlv_iterator_begin(&resp_iter, (uint8_t*)resp_buf->data + space_used, resp_buf->size - space_used);
+
+        for (int i = 0; (i < summary->num_results) && (rpc_status == TS_RPC_CALL_ACCEPTED); ++i) {
+
+            size_t serialised_len;
+            uint8_t *serialize_buf = serialize_test_result(&results[i], &serialised_len);
+
+            if (serialize_buf) {
+
+                struct tlv_record result_record;
+                result_record.tag = TS_TEST_RUNNER_TEST_RESULT_TAG;
+                result_record.length = serialised_len;
+                result_record.value = serialize_buf;
+
+                if (tlv_encode(&resp_iter, &result_record)) {
+
+                    space_used += tlv_required_space(serialised_len);
+                }
+                else {
+                    /* Insufficient buffer space */
+                    rpc_status = TS_RPC_ERROR_RESOURCE_FAILURE;
+                }
+
+                free(serialize_buf);
+            }
+        }
+    }
+
+    resp_buf->data_len = space_used;
+
+    return rpc_status;
+}
+
+/* Operation: run_tests */
+static rpc_status_t deserialize_run_tests_req(const struct call_param_buf *req_buf,
+        struct test_spec *test_spec)
+{
+    return deserialize_test_spec(req_buf, test_spec);
+}
+
+static rpc_status_t serialize_run_tests_resp(struct call_param_buf *resp_buf,
+        const struct test_summary *summary,
+        const struct test_result *results)
+{
+    return serialize_test_results(resp_buf, summary, results);
+}
+
+/* Operation: list_tests */
+static rpc_status_t deserialize_list_tests_req(const struct call_param_buf *req_buf,
+        struct test_spec *test_spec)
+{
+    return deserialize_test_spec(req_buf, test_spec);
+}
+
+static rpc_status_t serialize_list_tests_resp(struct call_param_buf *resp_buf,
+        const struct test_summary *summary,
+        const struct test_result *results)
+{
+    return serialize_test_results(resp_buf, summary, results);
+}
+
+/* Singleton method to provide access to the serializer instance */
+const struct test_runner_provider_serializer *packedc_test_runner_provider_serializer_instance(void)
+{
+    static const struct test_runner_provider_serializer instance = {
+        deserialize_run_tests_req,
+        serialize_run_tests_resp,
+        deserialize_list_tests_req,
+        serialize_list_tests_resp
+    };
+
+    return &instance;
+}
diff --git a/components/service/test_runner/provider/serializer/packed-c/packedc_test_runner_provider_serializer.h b/components/service/test_runner/provider/serializer/packed-c/packedc_test_runner_provider_serializer.h
new file mode 100644
index 0000000..c956bc3
--- /dev/null
+++ b/components/service/test_runner/provider/serializer/packed-c/packedc_test_runner_provider_serializer.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef PACKEDC_TEST_RUNNER_PROVIDER_SERIALIZER_H
+#define PACKEDC_TEST_RUNNER_PROVIDER_SERIALIZER_H
+
+#include <service/test_runner/provider/serializer/test_runner_provider_serializer.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Singleton method to provide access to the packed-c serializer
+ * for the test_runner service provider.
+ */
+const struct test_runner_provider_serializer *packedc_test_runner_provider_serializer_instance(void);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PACKEDC_TEST_RUNNER_PROVIDER_SERIALIZER_H */
diff --git a/components/service/test_runner/provider/serializer/test_runner_provider_serializer.h b/components/service/test_runner/provider/serializer/test_runner_provider_serializer.h
new file mode 100644
index 0000000..bd457f7
--- /dev/null
+++ b/components/service/test_runner/provider/serializer/test_runner_provider_serializer.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef TEST_RUNNER_PROVIDER_SERIALIZER_H
+#define TEST_RUNNER_PROVIDER_SERIALIZER_H
+
+#include <rpc/common/endpoint/rpc_interface.h>
+#include <service/test_runner/common/test_runner.h>
+
+/* Provides a common interface for parameter serialization operations
+ * for the test_runner service provider.  Allows alternative serialization
+ * protocols to be used without hard-wiring a particular protocol
+ * into the service provider code.  A concrete serializer must
+ * implement this interface.
+ */
+struct test_runner_provider_serializer {
+
+    /* Operation: run_tests */
+    rpc_status_t (*deserialize_run_tests_req)(const struct call_param_buf *req_buf,
+        struct test_spec *test_spec);
+
+    rpc_status_t (*serialize_run_tests_resp)(struct call_param_buf *resp_buf,
+        const struct test_summary *summary,
+        const struct test_result *results);
+
+    /* Operation: list_tests */
+    rpc_status_t (*deserialize_list_tests_req)(const struct call_param_buf *req_buf,
+        struct test_spec *test_spec);
+
+    rpc_status_t (*serialize_list_tests_resp)(struct call_param_buf *resp_buf,
+        const struct test_summary *summary,
+        const struct test_result *results);
+};
+
+#endif /* TEST_RUNNER_PROVIDER_SERIALIZER_H */
diff --git a/components/service/test_runner/provider/test_runner_backend.h b/components/service/test_runner/provider/test_runner_backend.h
new file mode 100644
index 0000000..9c9cbc8
--- /dev/null
+++ b/components/service/test_runner/provider/test_runner_backend.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef CPPUTEST_TEST_RUNNER_BACKEND_H
+#define CPPUTEST_TEST_RUNNER_BACKEND_H
+
+#include <stddef.h>
+#include <service/test_runner/common/test_runner.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct test_runner_provider;
+
+/**
+ * Provides an abstract interface for a backend test runner. A
+ * concrete implementation will map methods to a test framework
+ * such as CppUtets.  test_runner objects may be linked to
+ * accommodate a mix of backend test frameworks.
+ */
+struct test_runner_backend
+{
+	/* Return the number of tests that are qualified by the test spec */
+	size_t (*count_tests)(const struct test_spec *spec);
+
+	/* Run a set of tests according to the provided test_spec */
+	int (*run_tests)(const struct test_spec *spec,
+		struct test_summary *summary, struct test_result *results, size_t result_limit);
+
+	/* List a set of tests according to the provided test_spec */
+	void (*list_tests)(const struct test_spec *spec,
+		struct test_summary *summary, struct test_result *results, size_t result_limit);
+
+	/* Used by the test_runner_provider to maintain a linked list */
+	struct test_runner_backend *next;
+};
+
+/**
+ * A concrete test_runner may provide an implementation of this function if it
+ * is to be the default test runner for a deployment.  Additional test runners
+ * may be registered but there can only be one default for a deployment.
+ */
+void test_runner_register_default_backend(struct test_runner_provider *context);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* CPPUTEST_TEST_RUNNER_BACKEND_H */
diff --git a/components/service/test_runner/provider/test_runner_provider.c b/components/service/test_runner/provider/test_runner_provider.c
new file mode 100644
index 0000000..4b61bc1
--- /dev/null
+++ b/components/service/test_runner/provider/test_runner_provider.c
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include <stdlib.h>
+#include <stdbool.h>
+#include <protocols/service/test_runner/packed-c/opcodes.h>
+#include <protocols/service/test_runner/packed-c/status.h>
+#include <protocols/rpc/common/packed-c/status.h>
+#include "test_runner_provider.h"
+#include "test_runner_backend.h"
+
+/* Service request handlers */
+static rpc_status_t run_tests_handler(void *context, struct call_req* req);
+static rpc_status_t list_tests_handler(void *context, struct call_req* req);
+
+/* Handler mapping table for service */
+static const struct service_handler handler_table[] = {
+    {TS_TEST_RUNNER_OPCODE_RUN_TESTS,          run_tests_handler},
+    {TS_TEST_RUNNER_OPCODE_LIST_TESTS,         list_tests_handler}
+};
+
+struct rpc_interface *test_runner_provider_init(struct test_runner_provider *context)
+{
+    struct rpc_interface *rpc_interface = NULL;
+
+    if (context) {
+
+        for (size_t encoding = 0; encoding < TS_RPC_ENCODING_LIMIT; ++encoding)
+            context->serializers[encoding] = NULL;
+
+        context->backend_list = NULL;
+
+        service_provider_init(&context->base_provider, context,
+                    handler_table, sizeof(handler_table)/sizeof(struct service_handler));
+
+        rpc_interface = service_provider_get_rpc_interface(&context->base_provider);
+
+        /* Allow a deployment specific test_runner backend to be registrered */
+        test_runner_register_default_backend(context);
+    }
+
+    return rpc_interface;
+}
+
+void test_runner_provider_deinit(struct test_runner_provider *context)
+{
+    (void)context;
+}
+
+void test_runner_provider_register_serializer(struct test_runner_provider *context,
+                unsigned int encoding, const struct test_runner_provider_serializer *serializer)
+{
+    if (encoding < TS_RPC_ENCODING_LIMIT)
+        context->serializers[encoding] = serializer;
+}
+
+void test_runner_provider_register_backend(struct test_runner_provider *context,
+                struct test_runner_backend *backend)
+{
+    /* Insert into list of backend test runners */
+    backend->next = context->backend_list;
+    context->backend_list = backend;
+}
+
+static const struct test_runner_provider_serializer* get_test_runner_serializer(
+                struct test_runner_provider *context, const struct call_req *req)
+{
+    const struct test_runner_provider_serializer* serializer = NULL;
+    unsigned int encoding = call_req_get_encoding(req);
+
+    if (encoding < TS_RPC_ENCODING_LIMIT) serializer = context->serializers[encoding];
+
+    return serializer;
+}
+
+static struct test_result *alloc_result_buf(struct test_runner_provider *context,
+                const struct test_spec *test_spec, size_t *result_limit)
+{
+    struct test_result *space = NULL;
+    size_t total_tests = 0;
+    struct test_runner_backend *backend = context->backend_list;
+
+    while (backend) {
+
+        total_tests += backend->count_tests(test_spec);
+        backend = backend->next;
+    }
+
+    space = malloc(total_tests * sizeof(struct test_result));
+
+    *result_limit = total_tests;
+    return space;
+}
+
+static int run_qualifying_tests(struct test_runner_provider *context, bool list_only, const struct test_spec *spec,
+		        struct test_summary *summary, struct test_result *results, size_t result_limit)
+{
+    int test_status = TS_TEST_RUNNER_STATUS_SUCCESS;
+    size_t results_used = 0;
+    struct test_runner_backend *backend = context->backend_list;
+
+    summary->num_tests = 0;
+	summary->num_results = 0;
+	summary->num_passed = 0;
+	summary->num_failed = 0;
+
+    while (backend && (test_status == TS_TEST_RUNNER_STATUS_SUCCESS)) {
+
+        struct test_summary interim_summary;
+
+        if (list_only) {
+
+            backend->list_tests(spec, &interim_summary,
+                            &results[summary->num_results],
+                            result_limit - summary->num_results);
+        }
+        else {
+
+            test_status = backend->run_tests(spec, &interim_summary,
+                            &results[summary->num_results],
+                            result_limit - summary->num_results);
+        }
+
+        summary->num_tests += interim_summary.num_tests;
+	    summary->num_results += interim_summary.num_results;
+	    summary->num_passed += interim_summary.num_passed;
+	    summary->num_failed += interim_summary.num_failed;
+
+        backend = backend->next;
+    }
+
+    return test_status;
+}
+
+static rpc_status_t run_tests_handler(void *context, struct call_req* req)
+{
+    struct test_runner_provider *this_instance = (struct test_runner_provider*)context;
+    rpc_status_t rpc_status = TS_RPC_ERROR_SERIALIZATION_NOT_SUPPORTED;
+    struct test_spec test_spec;
+
+    struct call_param_buf *req_buf = call_req_get_req_buf(req);
+    const struct test_runner_provider_serializer *serializer = get_test_runner_serializer(this_instance, req);
+
+    if (serializer)
+        rpc_status = serializer->deserialize_run_tests_req(req_buf, &test_spec);
+
+    if (rpc_status == TS_RPC_CALL_ACCEPTED) {
+
+        int test_status;
+        struct test_summary summary;
+        size_t result_limit = 0;
+        struct test_result *result_buf = alloc_result_buf(this_instance, &test_spec, &result_limit);
+
+        test_status = run_qualifying_tests(this_instance, false, &test_spec, &summary, result_buf, result_limit);
+
+        call_req_set_opstatus(req, test_status);
+
+        if (test_status == TS_TEST_RUNNER_STATUS_SUCCESS) {
+
+            struct call_param_buf *resp_buf = call_req_get_resp_buf(req);
+            rpc_status = serializer->serialize_run_tests_resp(resp_buf, &summary, result_buf);
+
+            free(result_buf);
+        }
+    }
+
+    return rpc_status;
+}
+
+static rpc_status_t list_tests_handler(void *context, struct call_req* req)
+{
+    struct test_runner_provider *this_instance = (struct test_runner_provider*)context;
+    rpc_status_t rpc_status = TS_RPC_ERROR_SERIALIZATION_NOT_SUPPORTED;
+    struct test_spec test_spec;
+
+    struct call_param_buf *req_buf = call_req_get_req_buf(req);
+    const struct test_runner_provider_serializer *serializer = get_test_runner_serializer(this_instance, req);
+
+    if (serializer)
+        rpc_status = serializer->deserialize_list_tests_req(req_buf, &test_spec);
+
+    if (rpc_status == TS_RPC_CALL_ACCEPTED) {
+
+        int test_status;
+        struct test_summary summary;
+        size_t result_limit = 0;
+        struct test_result *result_buf = alloc_result_buf(this_instance, &test_spec, &result_limit);
+
+        test_status = run_qualifying_tests(this_instance, true, &test_spec, &summary, result_buf, result_limit);
+
+        call_req_set_opstatus(req, test_status);
+
+        if (test_status == TS_TEST_RUNNER_STATUS_SUCCESS) {
+
+            struct call_param_buf *resp_buf = call_req_get_resp_buf(req);
+            rpc_status = serializer->serialize_list_tests_resp(resp_buf, &summary, result_buf);
+
+            free(result_buf);
+        }
+    }
+
+    return rpc_status;
+}
\ No newline at end of file
diff --git a/components/service/test_runner/provider/test_runner_provider.h b/components/service/test_runner/provider/test_runner_provider.h
new file mode 100644
index 0000000..e5c9230
--- /dev/null
+++ b/components/service/test_runner/provider/test_runner_provider.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef TEST_RUNNER_PROVIDER_H
+#define TEST_RUNNER_PROVIDER_H
+
+#include <rpc/common/endpoint/rpc_interface.h>
+#include <rpc_caller.h>
+#include <service/common/provider/service_provider.h>
+#include <service/test_runner/provider/serializer/test_runner_provider_serializer.h>
+#include <protocols/rpc/common/packed-c/encoding.h>
+#include "test_runner_backend.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* test_runner_provider serice provider state */
+struct test_runner_provider
+{
+    struct service_provider base_provider;
+    const struct test_runner_provider_serializer *serializers[TS_RPC_ENCODING_LIMIT];
+    struct test_runner_backend *backend_list;
+};
+
+struct rpc_interface *test_runner_provider_init(struct test_runner_provider *context);
+
+void test_runner_provider_deinit(struct test_runner_provider *context);
+
+void test_runner_provider_register_serializer(struct test_runner_provider *context,
+                    unsigned int encoding, const struct test_runner_provider_serializer *serializer);
+
+void test_runner_provider_register_backend(struct test_runner_provider *context,
+                    struct test_runner_backend *backend);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* CPPUTEST_TEST_RUNNER_PROVIDER_H */
diff --git a/components/service/test_runner/test/service/component.cmake b/components/service/test_runner/test/service/component.cmake
new file mode 100644
index 0000000..7cbd452
--- /dev/null
+++ b/components/service/test_runner/test/service/component.cmake
@@ -0,0 +1,14 @@
+#-------------------------------------------------------------------------------
+# 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}/test_runner_service_tests.cpp"
+	)
+
diff --git a/components/service/test_runner/test/service/test_runner_service_tests.cpp b/components/service/test_runner/test/service/test_runner_service_tests.cpp
new file mode 100644
index 0000000..c4ba0e0
--- /dev/null
+++ b/components/service/test_runner/test/service/test_runner_service_tests.cpp
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <service/test_runner/client/cpp/test_runner_client.h>
+#include <protocols/rpc/common/packed-c/encoding.h>
+#include <protocols/service/test_runner/packed-c/status.h>
+#include <service_locator.h>
+#include <CppUTest/TestHarness.h>
+#include <vector>
+#include <cstring>
+
+/*
+ * Service-level tests for the test_runner service.  These tests assume
+ * that the mock_test_runner backend is used as the only registered
+ * backend.  It includes some referenece test cases that are assumed
+ * by these tests.
+ */
+TEST_GROUP(TestRunnerServiceTests)
+{
+    void setup()
+    {
+        struct rpc_caller *caller;
+        int status;
+
+        m_rpc_session_handle = NULL;
+        m_test_runner_service_context = NULL;
+        m_test_runner_client = NULL;
+
+        service_locator_init();
+
+        m_test_runner_service_context = service_locator_query("sn:trustedfirmware.org:test-runner:0", &status);
+        CHECK(m_test_runner_service_context);
+
+        m_rpc_session_handle = service_context_open(m_test_runner_service_context, TS_RPC_ENCODING_PACKED_C, &caller);
+        CHECK(m_rpc_session_handle);
+
+        m_test_runner_client = new test_runner_client(caller);
+    }
+
+    void teardown()
+    {
+        delete m_test_runner_client;
+        m_test_runner_client = NULL;
+
+        service_context_close(m_test_runner_service_context, m_rpc_session_handle);
+        m_rpc_session_handle = NULL;
+
+        service_context_relinquish(m_test_runner_service_context);
+        m_test_runner_service_context = NULL;
+    }
+
+    rpc_session_handle m_rpc_session_handle;
+    struct service_context *m_test_runner_service_context;
+    test_runner_client *m_test_runner_client;
+};
+
+TEST(TestRunnerServiceTests, listAllTests)
+{
+    int test_status;
+    struct test_spec spec;
+    struct test_summary summary;
+    std::vector<struct test_result> results;
+
+    /* Create spec that qualifies all tests */
+    spec.name[0] = 0;
+    spec.group[0] = 0;
+
+    test_status = m_test_runner_client->list_tests(spec, summary, results);
+
+    CHECK_EQUAL(TS_TEST_RUNNER_STATUS_SUCCESS, test_status);
+
+    /* Check test summary */
+    CHECK_EQUAL(4, summary.num_tests);
+    CHECK_EQUAL(4, summary.num_results);
+    CHECK_EQUAL(0, summary.num_passed);
+    CHECK_EQUAL(0, summary.num_failed);
+
+    /* Check each test result is listed but not run */
+    CHECK(strcmp(results[0].group, "PlatformTests") == 0);
+    CHECK(strcmp(results[0].name, "Trng") == 0);
+    CHECK_EQUAL(TEST_RUN_STATE_NOT_RUN, results[0].run_state);
+
+    CHECK(strcmp(results[1].group, "PlatformTests") == 0);
+    CHECK(strcmp(results[1].name, "CheckIOmap") == 0);
+    CHECK_EQUAL(TEST_RUN_STATE_NOT_RUN, results[1].run_state);
+
+    CHECK(strcmp(results[2].group, "ConfigTests") == 0);
+    CHECK(strcmp(results[2].name, "ValidateConfig") == 0);
+    CHECK_EQUAL(TEST_RUN_STATE_NOT_RUN, results[2].run_state);
+
+    CHECK(strcmp(results[3].group, "ConfigTests") == 0);
+    CHECK(strcmp(results[3].name, "ApplyConfig") == 0);
+    CHECK_EQUAL(TEST_RUN_STATE_NOT_RUN, results[3].run_state);
+}
+
+TEST(TestRunnerServiceTests, runAllTests)
+{
+    int test_status;
+    struct test_spec spec;
+    struct test_summary summary;
+    std::vector<struct test_result> results;
+
+    /* Create spec that qualifies all tests */
+    spec.name[0] = 0;
+    spec.group[0] = 0;
+
+    test_status = m_test_runner_client->run_tests(spec, summary, results);
+
+    CHECK_EQUAL(TS_TEST_RUNNER_STATUS_SUCCESS, test_status);
+
+    CHECK_EQUAL(4, summary.num_tests);
+    CHECK_EQUAL(4, summary.num_results);
+    CHECK_EQUAL(3, summary.num_passed);
+    CHECK_EQUAL(1, summary.num_failed);
+
+    /* Check each test result has run with the expected outcome */
+    CHECK(strcmp(results[0].group, "PlatformTests") == 0);
+    CHECK(strcmp(results[0].name, "Trng") == 0);
+    CHECK_EQUAL(TEST_RUN_STATE_PASSED, results[0].run_state);
+
+    CHECK(strcmp(results[1].group, "PlatformTests") == 0);
+    CHECK(strcmp(results[1].name, "CheckIOmap") == 0);
+    CHECK_EQUAL(TEST_RUN_STATE_PASSED, results[1].run_state);
+
+    CHECK(strcmp(results[2].group, "ConfigTests") == 0);
+    CHECK(strcmp(results[2].name, "ValidateConfig") == 0);
+    CHECK_EQUAL(TEST_RUN_STATE_FAILED, results[2].run_state);
+
+    CHECK(strcmp(results[3].group, "ConfigTests") == 0);
+    CHECK(strcmp(results[3].name, "ApplyConfig") == 0);
+    CHECK_EQUAL(TEST_RUN_STATE_PASSED, results[3].run_state);
+}
+
+TEST(TestRunnerServiceTests, listPlatformTests)
+{
+    int test_status;
+    struct test_spec spec;
+    struct test_summary summary;
+    std::vector<struct test_result> results;
+
+    /* Create spec that qualifies all tests in a group*/
+    spec.name[0] = 0;
+    strcpy(spec.group, "PlatformTests");
+
+    test_status = m_test_runner_client->list_tests(spec, summary, results);
+
+    CHECK_EQUAL(TS_TEST_RUNNER_STATUS_SUCCESS, test_status);
+
+    /* Check test summary */
+    CHECK_EQUAL(2, summary.num_tests);
+    CHECK_EQUAL(2, summary.num_results);
+    CHECK_EQUAL(0, summary.num_passed);
+    CHECK_EQUAL(0, summary.num_failed);
+}
+
+TEST(TestRunnerServiceTests, runConfigTests)
+{
+    int test_status;
+    struct test_spec spec;
+    struct test_summary summary;
+    std::vector<struct test_result> results;
+
+    /* Create spec that qualifies all tests in a group*/
+    spec.name[0] = 0;
+    strcpy(spec.group, "ConfigTests");
+
+    test_status = m_test_runner_client->run_tests(spec, summary, results);
+
+    CHECK_EQUAL(TS_TEST_RUNNER_STATUS_SUCCESS, test_status);
+
+    /* Check test summary */
+    CHECK_EQUAL(2, summary.num_tests);
+    CHECK_EQUAL(2, summary.num_results);
+    CHECK_EQUAL(1, summary.num_passed);
+    CHECK_EQUAL(1, summary.num_failed);
+}
+
+TEST(TestRunnerServiceTests, runSpecificTest)
+{
+    int test_status;
+    struct test_spec spec;
+    struct test_summary summary;
+    std::vector<struct test_result> results;
+
+    /* Create spec that qualifies a specific test */
+    strcpy(spec.name, "ValidateConfig");
+    strcpy(spec.group, "ConfigTests");
+
+    test_status = m_test_runner_client->run_tests(spec, summary, results);
+
+    CHECK_EQUAL(TS_TEST_RUNNER_STATUS_SUCCESS, test_status);
+
+    /* Check test summary */
+    CHECK_EQUAL(1, summary.num_tests);
+    CHECK_EQUAL(1, summary.num_results);
+    CHECK_EQUAL(0, summary.num_passed);
+    CHECK_EQUAL(1, summary.num_failed);
+}
diff --git a/deployments/component-test/component-test.cmake b/deployments/component-test/component-test.cmake
index 2d7c21a..0e6fd1b 100644
--- a/deployments/component-test/component-test.cmake
+++ b/deployments/component-test/component-test.cmake
@@ -36,6 +36,7 @@
 		"components/service/locator/test"
 		"components/service/locator/standalone"
 		"components/service/locator/standalone/services/crypto"
+		"components/service/locator/standalone/services/test-runner"
 		"components/service/crypto/client/cpp"
 		"components/service/crypto/client/cpp/protobuf"
 		"components/service/crypto/client/cpp/packed-c"
@@ -55,6 +56,9 @@
 		"components/service/secure_storage/provider/secure_flash_store/flash_fs"
 		"components/service/secure_storage/provider/secure_flash_store/flash"
 		"components/service/secure_storage/test"
+		"components/service/test_runner/provider"
+		"components/service/test_runner/provider/serializer/packed-c"
+		"components/service/test_runner/provider/backend/null"
 		"protocols/rpc/common/protobuf"
 		"protocols/rpc/common/packed-c"
 		"protocols/service/crypto/packed-c"
diff --git a/deployments/env-test/opteesp/.gitignore b/deployments/env-test/opteesp/.gitignore
new file mode 100644
index 0000000..378eac2
--- /dev/null
+++ b/deployments/env-test/opteesp/.gitignore
@@ -0,0 +1 @@
+build
diff --git a/deployments/env-test/opteesp/CMakeLists.txt b/deployments/env-test/opteesp/CMakeLists.txt
new file mode 100644
index 0000000..125485d
--- /dev/null
+++ b/deployments/env-test/opteesp/CMakeLists.txt
@@ -0,0 +1,129 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+cmake_minimum_required(VERSION 3.16)
+include(../../deployment.cmake REQUIRED)
+
+#-------------------------------------------------------------------------------
+#  The CMakeLists.txt for building the env-test deployment for opteesp
+#
+#  Builds the test_runner service provider for running in an SEL0 secure partition
+#  hosted by OPTEE in the role of SPM.  Environment tests are added and CppUnit
+#  test cases.
+#-------------------------------------------------------------------------------
+include(${TS_ROOT}/environments/opteesp/env.cmake)
+project(trusted-services LANGUAGES C CXX ASM)
+add_executable(env_test)
+target_include_directories(env_test PRIVATE "${TOP_LEVEL_INCLUDE_DIRS}")
+set(SP_UUID "33c75baf-ac6a-4fe4-8ac7-e9909bee2d17")
+
+
+# Include SP DEV KIT interface
+set(SP_DEV_KIT_INC_DIR ${CMAKE_CURRENT_LIST_DIR})
+list(APPEND CMAKE_MODULE_PATH "${TS_ROOT}/external/Spdevkit")
+find_package(Spdevkit REQUIRED)
+sp_dev_kit_configure_linking(TARGET env_test DEFINES ARM64=1)
+target_link_libraries(env_test PRIVATE ${SP_DEV_KIT_LIBRARIES})
+
+#-------------------------------------------------------------------------------
+#  Components that are env_testecific to deployment in the opteesp
+#  environment.
+#-------------------------------------------------------------------------------
+add_components(TARGET "env_test"
+	BASE_DIR ${TS_ROOT}
+	COMPONENTS
+		"components/common/tlv"
+		"components/config/ramstore"
+		"components/messaging/ffa/libsp"
+		"components/rpc/ffarpc/endpoint"
+		"components/rpc/common/interface"
+		"components/service/common"
+		"components/service/common/provider"
+		"components/service/test_runner/provider"
+		"components/service/test_runner/provider/serializer/packed-c"
+		"components/service/test_runner/provider/backend/mock"
+		"protocols/rpc/common/packed-c"
+		"environments/opteesp"
+)
+
+target_sources(env_test PRIVATE
+	env_test.c
+	env_test_config.c
+)
+
+#-------------------------------------------------------------------------------
+#  Use the selected platform to provide drivers needed by the deployment
+#
+#-------------------------------------------------------------------------------
+# temporarily force platform - remove when external builder updated
+set(TS_PLATFORM "arm/fvp/fvp_base_revc-2xaemv8a" CACHE STRING "Overridden" FORCE)
+
+add_platform(TARGET "env_test")
+
+#-------------------------------------------------------------------------------
+#  Components used from external projects
+#
+#-------------------------------------------------------------------------------
+
+if(CMAKE_CROSSCOMPILING)
+	target_link_libraries(env_test PRIVATE stdc++ gcc m)
+endif()
+
+#################################################################
+
+target_compile_definitions(env_test PRIVATE
+	ARM64=1
+)
+
+target_include_directories(env_test PRIVATE
+	${TS_ROOT}
+	${TS_ROOT}/components
+	${TS_ROOT}/deployments/env-test/opteesp
+)
+
+if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
+	target_compile_options(env_test PRIVATE
+		-fdiagnostics-show-option
+		-fpic
+		-gdwarf-2
+		-mstrict-align
+		-O0
+		$<$<COMPILE_LANGUAGE:C>:-std=gnu99>
+		$<$<COMPILE_LANGUAGE:CXX>:-fno-use-cxa-atexit>
+	)
+
+	# Options for GCC that control linking
+	target_link_options(env_test PRIVATE
+		-e __sp_entry
+		-fno-lto
+		-nostdlib
+		-pie
+		-zmax-page-size=4096
+	)
+	# Options directly for LD, these are not understood by GCC
+	target_link_options(env_test PRIVATE
+		-Wl,--as-needed
+		-Wl,--sort-section=alignment
+		# -Wl,--dynamic-list ${CMAKE_CURRENT_LIST_DIR}/dyn_list
+	)
+endif()
+
+compiler_generate_stripped_elf(TARGET env_test NAME "${SP_UUID}.stripped.elf" RES STRIPPED_ELF)
+
+######################################## install
+if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+	set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install CACHE PATH "location to install build output to." FORCE)
+endif()
+#TODO: api headers
+install(TARGETS env_test
+			PUBLIC_HEADER DESTINATION include
+			RUNTIME DESTINATION bin
+		)
+install(FILES ${STRIPPED_ELF} DESTINATION bin)
+
+set(EXPORT_SP_NAME "env-test")
+set(EXPORT_SP_UUID ${SP_UUID})
+include(${TS_ROOT}/environments/opteesp/ExportSp.cmake)
diff --git a/deployments/env-test/opteesp/env_test.c b/deployments/env-test/opteesp/env_test.c
new file mode 100644
index 0000000..0bb523f
--- /dev/null
+++ b/deployments/env-test/opteesp/env_test.c
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: BSD-3-Clause
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ */
+
+#include <rpc/ffarpc/caller/sp/ffarpc_caller.h>
+#include <rpc/ffarpc/endpoint/ffarpc_call_ep.h>
+#include <service/test_runner/provider/test_runner_provider.h>
+#include <service/test_runner/provider/serializer/packed-c/packedc_test_runner_provider_serializer.h>
+#include <protocols/rpc/common/packed-c/status.h>
+#include <ffa_api.h>
+#include <sp_api.h>
+#include <sp_rxtx.h>
+#include <trace.h>
+#include "env_test_config.h"
+
+
+uint16_t own_id = 0; /* !!Needs refactoring as parameter to ffarpc_caller_init */
+
+
+static int sp_init(uint16_t *own_sp_id);
+
+void __noreturn sp_main(struct ffa_init_info *init_info)
+{
+	struct test_runner_provider test_runner_provider;
+	struct ffa_call_ep ffarpc_call_ep;
+	struct rpc_interface *test_runner_iface;
+	struct ffarpc_caller ffarpc_caller;
+	struct ffa_direct_msg req_msg;
+
+	/* Boot */
+	(void) init_info;
+
+	if (sp_init(&own_id) != 0) goto fatal_error;
+
+	load_sp_config(init_info);
+
+	/* Initialize the test_runner service */
+	test_runner_iface = test_runner_provider_init(&test_runner_provider);
+
+	test_runner_provider_register_serializer(&test_runner_provider,
+            TS_RPC_ENCODING_PACKED_C, packedc_test_runner_provider_serializer_instance());
+
+	ffa_call_ep_init(&ffarpc_call_ep, test_runner_iface);
+
+ 	/* End of boot phase */
+	ffa_msg_wait(&req_msg);
+
+	while (1) {
+		if (req_msg.function_id == FFA_MSG_SEND_DIRECT_REQ_32) {
+
+			struct ffa_direct_msg resp_msg;
+
+			ffa_call_ep_receive(&ffarpc_call_ep, &req_msg, &resp_msg);
+
+			ffa_msg_send_direct_resp(req_msg.destination_id,
+					req_msg.source_id, resp_msg.args[0], resp_msg.args[1],
+					resp_msg.args[2], resp_msg.args[3], resp_msg.args[4],
+					&req_msg);
+		}
+	}
+
+fatal_error:
+	/* SP is not viable */
+	EMSG("environment-test SP error");
+	while (1) {}
+}
+
+void sp_interrupt_handler(uint32_t interrupt_id)
+{
+	(void)interrupt_id;
+}
+
+static int sp_init(uint16_t *own_sp_id)
+{
+	int status = -1;
+	ffa_result ffa_res;
+	sp_result sp_res;
+	static uint8_t tx_buffer[4096] __aligned(4096);
+	static uint8_t rx_buffer[4096] __aligned(4096);
+
+	sp_res = sp_rxtx_buffer_map(tx_buffer, rx_buffer, sizeof(rx_buffer));
+	if (sp_res == SP_RESULT_OK) {
+		ffa_res = ffa_id_get(own_sp_id);
+		if (ffa_res == FFA_OK) {
+			status = 0;
+		}
+	}
+
+	return status;
+}
diff --git a/deployments/env-test/opteesp/env_test.h b/deployments/env-test/opteesp/env_test.h
new file mode 100644
index 0000000..0f4c8b7
--- /dev/null
+++ b/deployments/env-test/opteesp/env_test.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef ENV_TEST_SP_H
+#define ENV_TEST_SP_H
+
+#define ENV_TEST_SP_UUID \
+	 {0x33c75baf, 0xac6a, 0x4fe4, \
+                {0x8a, 0xc7, 0xe9, 0x90, 0x9b, 0xee, 0x2d, 0x17}}
+
+#define ENV_TEST_SP_UUID_BYTES \
+	{0x33, 0xc7, 0x5b, 0xaf, 0xac, 0x6a, 0x4f, 0xef, \
+    0x8a, 0xcy, 0xe9, 0x90, 0x9b, 0xee, 0x2d, 0x17}
+
+#endif /* ENV_TEST_SP_H */
diff --git a/deployments/env-test/opteesp/env_test_config.c b/deployments/env-test/opteesp/env_test_config.c
new file mode 100644
index 0000000..14a1e53
--- /dev/null
+++ b/deployments/env-test/opteesp/env_test_config.c
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: BSD-3-Clause
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ */
+
+#include <config/ramstore/config_ramstore.h>
+#include "env_test_config.h"
+
+
+void load_sp_config(struct ffa_init_info *init_info)
+{
+	config_ramstore_init();
+
+	/* Load deployment specific configuration */
+	(void)init_info;
+}
\ No newline at end of file
diff --git a/deployments/env-test/opteesp/env_test_config.h b/deployments/env-test/opteesp/env_test_config.h
new file mode 100644
index 0000000..8ed4a7e
--- /dev/null
+++ b/deployments/env-test/opteesp/env_test_config.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef ENV_TEST_CONFIG_H
+#define ENV_TEST_CONFIG_H
+
+#include <ffa_api.h>
+
+/**
+ * Loads the SP specific configuration passed as SP initialization parameters.
+ */
+void load_sp_config(struct ffa_init_info *init_info);
+
+
+#endif /* ENV_TEST_CONFIG_H */
diff --git a/deployments/env-test/opteesp/optee_sp_user_defines.h b/deployments/env-test/opteesp/optee_sp_user_defines.h
new file mode 100644
index 0000000..a524a6e
--- /dev/null
+++ b/deployments/env-test/opteesp/optee_sp_user_defines.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef SP_HEADER_DEFINES_H
+#define SP_HEADER_DEFINES_H
+
+/* To get UUID definition */
+#include "env_test.h"
+
+#define OPTEE_SP_UUID             ENV_TEST_SP_UUID
+#define OPTEE_SP_FLAGS				0
+
+/* Provisioned stack size */
+#define OPTEE_SP_STACK_SIZE			(64 * 1024)
+
+/* Provisioned heap size */
+#define OPTEE_SP_HEAP_SIZE			(32 * 1024)
+
+#endif /* SP_HEADER_DEFINES_H */
diff --git a/deployments/libts/linux-pc/CMakeLists.txt b/deployments/libts/linux-pc/CMakeLists.txt
index 7924f7c..01387bc 100644
--- a/deployments/libts/linux-pc/CMakeLists.txt
+++ b/deployments/libts/linux-pc/CMakeLists.txt
@@ -36,6 +36,7 @@
 		"components/service/common/provider"
 		"components/service/locator/standalone"
 		"components/service/locator/standalone/services/crypto"
+		"components/service/locator/standalone/services/test-runner"
 		"components/service/crypto/provider/mbedcrypto"
 		"components/service/crypto/provider/mbedcrypto/trng_adapter/linux"
 		"components/service/crypto/provider/serializer/protobuf"
@@ -44,6 +45,9 @@
 		"components/service/secure_storage/provider/secure_flash_store"
 		"components/service/secure_storage/provider/secure_flash_store/flash_fs"
 		"components/service/secure_storage/provider/secure_flash_store/flash"
+		"components/service/test_runner/provider"
+		"components/service/test_runner/provider/serializer/packed-c"
+		"components/service/test_runner/provider/backend/mock"
 		"protocols/rpc/common/packed-c"
 		"protocols/service/crypto/packed-c"
 		"protocols/service/crypto/protobuf"
diff --git a/deployments/ts-remote-test/arm-linux/.gitignore b/deployments/ts-remote-test/arm-linux/.gitignore
new file mode 100644
index 0000000..378eac2
--- /dev/null
+++ b/deployments/ts-remote-test/arm-linux/.gitignore
@@ -0,0 +1 @@
+build
diff --git a/deployments/ts-remote-test/arm-linux/CMakeLists.txt b/deployments/ts-remote-test/arm-linux/CMakeLists.txt
new file mode 100644
index 0000000..7540178
--- /dev/null
+++ b/deployments/ts-remote-test/arm-linux/CMakeLists.txt
@@ -0,0 +1,31 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+cmake_minimum_required(VERSION 3.16)
+include(../../deployment.cmake REQUIRED)
+
+#-------------------------------------------------------------------------------
+#  The CMakeLists.txt for building the ts-remote-test deployment for arm-linux
+#
+#-------------------------------------------------------------------------------
+include(${TS_ROOT}/environments/arm-linux/env.cmake)
+project(trusted-services LANGUAGES CXX C)
+add_executable(ts-remote-test)
+target_include_directories(ts-remote-test PRIVATE "${TOP_LEVEL_INCLUDE_DIRS}")
+
+#-------------------------------------------------------------------------------
+#  Extend with components that are common across all deployments of
+#  ts-remote-test
+#
+#-------------------------------------------------------------------------------
+include(../ts-remote-test.cmake REQUIRED)
+
+#-------------------------------------------------------------------------------
+#  Define library options and dependencies.
+#
+#-------------------------------------------------------------------------------
+env_set_link_options(TGT ts-remote-test)
+target_link_libraries(ts-remote-test PRIVATE stdc++ gcc m)
diff --git a/deployments/ts-remote-test/linux-pc/CMakeLists.txt b/deployments/ts-remote-test/linux-pc/CMakeLists.txt
new file mode 100644
index 0000000..5e5aaa5
--- /dev/null
+++ b/deployments/ts-remote-test/linux-pc/CMakeLists.txt
@@ -0,0 +1,24 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+cmake_minimum_required(VERSION 3.16)
+include(../../deployment.cmake REQUIRED)
+
+#-------------------------------------------------------------------------------
+#  The CMakeLists.txt for building the ts-remote-test deployment for linux-pc
+#
+#-------------------------------------------------------------------------------
+include(${TS_ROOT}/environments/linux-pc/env.cmake)
+project(trusted-services LANGUAGES CXX C)
+add_executable(ts-remote-test)
+target_include_directories(ts-remote-test PRIVATE "${TOP_LEVEL_INCLUDE_DIRS}")
+
+#-------------------------------------------------------------------------------
+#  Extend with components that are common across all deployments of
+#  ts-remote-test
+#
+#-------------------------------------------------------------------------------
+include(../ts-remote-test.cmake REQUIRED)
diff --git a/deployments/ts-remote-test/ts-remote-test.cmake b/deployments/ts-remote-test/ts-remote-test.cmake
new file mode 100644
index 0000000..7e5b286
--- /dev/null
+++ b/deployments/ts-remote-test/ts-remote-test.cmake
@@ -0,0 +1,50 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+
+#-------------------------------------------------------------------------------
+#  The base build file shared between deployments of 'ts-remote-test' for
+#  different environments.  Acts as a client for tests running in a remote
+#  processing environment.
+#-------------------------------------------------------------------------------
+
+#-------------------------------------------------------------------------------
+#  Use libts for locating and accessing services. An appropriate version of
+#  libts will be imported for the enviroment in which tests are
+#  deployed.
+#-------------------------------------------------------------------------------
+include(${TS_ROOT}/deployments/libts/libts-import.cmake)
+target_link_libraries(ts-remote-test PRIVATE libts)
+
+#-------------------------------------------------------------------------------
+#  Common main for all deployments
+#
+#-------------------------------------------------------------------------------
+target_sources(ts-remote-test PRIVATE
+	"${CMAKE_CURRENT_LIST_DIR}/ts-remote-test.cpp"
+)
+
+#-------------------------------------------------------------------------------
+#  Components that are common accross all deployments
+#
+#-------------------------------------------------------------------------------
+add_components(
+	TARGET "ts-remote-test"
+	BASE_DIR ${TS_ROOT}
+	COMPONENTS
+		"components/app/remote-test-runner"
+		"components/common/tlv"
+		"components/service/test_runner/client/cpp"
+)
+
+#-------------------------------------------------------------------------------
+#  Define install content.
+#
+#-------------------------------------------------------------------------------
+if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+	set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install CACHE PATH "location to install build output to." FORCE)
+endif()
+install(TARGETS ts-remote-test RUNTIME DESTINATION bin)
\ No newline at end of file
diff --git a/deployments/ts-remote-test/ts-remote-test.cpp b/deployments/ts-remote-test/ts-remote-test.cpp
new file mode 100644
index 0000000..1c708f6
--- /dev/null
+++ b/deployments/ts-remote-test/ts-remote-test.cpp
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: BSD-2-Clause
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ */
+
+#include <service/test_runner/client/cpp/test_runner_client.h>
+#include <app/remote-test-runner/remote_test_runner.h>
+#include <protocols/rpc/common/packed-c/encoding.h>
+#include <service_locator.h>
+#include <rpc_caller.h>
+#include <cstdio>
+
+int main(int argc, char *argv[]) {
+	(void) argc;
+	(void) argv;
+
+	int status = -1;
+	struct service_context *test_runner_service_context = NULL;
+
+	service_locator_init();
+
+	test_runner_service_context = service_locator_query("sn:trustedfirmware.org:test-runner:0", &status);
+
+	if (test_runner_service_context) {
+
+		struct rpc_caller *caller;
+		rpc_session_handle rpc_session_handle;
+
+		rpc_session_handle = service_context_open(test_runner_service_context, TS_RPC_ENCODING_PACKED_C, &caller);
+
+		if (rpc_session_handle) {
+
+			test_runner_client test_runner_client(caller);
+			remote_test_runner commandline_runner(&test_runner_client);
+
+			status = commandline_runner.execute(argc, argv);
+
+			if (status != 0) {
+				printf("Command failed with test status: %d rpc status: %d\n", status, test_runner_client.err_rpc_status());
+			}
+
+			service_context_close(test_runner_service_context, rpc_session_handle);
+		}
+		else {
+			printf("Failed to open rpc session\n");
+		}
+
+		service_context_relinquish(test_runner_service_context);
+	}
+	else {
+		printf("Failed to discover test_runner service\n");
+	}
+
+	return status;
+}
diff --git a/deployments/ts-service-test/linux-pc/CMakeLists.txt b/deployments/ts-service-test/linux-pc/CMakeLists.txt
index 8ccc3b3..85a0a36 100644
--- a/deployments/ts-service-test/linux-pc/CMakeLists.txt
+++ b/deployments/ts-service-test/linux-pc/CMakeLists.txt
@@ -64,6 +64,18 @@
 target_include_directories(ts-service-test PRIVATE "${TOP_LEVEL_INCLUDE_DIRS}")
 
 #-------------------------------------------------------------------------------
+#  Components that are specific to deployment in the linux-pc environment.
+#
+#-------------------------------------------------------------------------------
+add_components(
+	TARGET "ts-service-test"
+	BASE_DIR ${TS_ROOT}
+    COMPONENTS
+        "components/service/test_runner/client/cpp"
+		"components/service/test_runner/test/service"
+)
+
+#-------------------------------------------------------------------------------
 #  Extend with components that are common across all deployments of
 #  ts-service-test
 #
diff --git a/protocols/service/test_runner/packed-c/list_tests.h b/protocols/service/test_runner/packed-c/list_tests.h
new file mode 100644
index 0000000..25bab9e
--- /dev/null
+++ b/protocols/service/test_runner/packed-c/list_tests.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef TS_TEST_RUNNER_LIST_TESTS
+#define TS_TEST_RUNNER_LIST_TESTS
+
+/**
+ * Input parmeters consist of test spec (defined in test_spec.h) to
+ * define the set of tests to run.
+ */
+#include "test_spec.h"
+
+/* Output parameters are the same as for run_tests except no
+ * tests are actually run.
+ */
+#include "test_result.h"
+
+#endif /* TS_TEST_RUNNER_LIST_TESTS */
diff --git a/protocols/service/test_runner/packed-c/opcodes.h b/protocols/service/test_runner/packed-c/opcodes.h
new file mode 100644
index 0000000..e28da24
--- /dev/null
+++ b/protocols/service/test_runner/packed-c/opcodes.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef TS_TEST_RUNNER_OPCODES_H
+#define TS_TEST_RUNNER_OPCODES_H
+
+/**
+ *  C/C++ definition of test_runner service opcodes
+ */
+#define TS_TEST_RUNNER_OPCODE_RUN_TESTS           (0x0101)
+#define TS_TEST_RUNNER_OPCODE_LIST_TESTS          (0x0102)
+
+#endif /* TS_TEST_RUNNER_OPCODES_H */
diff --git a/protocols/service/test_runner/packed-c/run_tests.h b/protocols/service/test_runner/packed-c/run_tests.h
new file mode 100644
index 0000000..9b25fa6
--- /dev/null
+++ b/protocols/service/test_runner/packed-c/run_tests.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef TS_TEST_RUNNER_RUN_TESTS
+#define TS_TEST_RUNNER_RUN_TESTS
+
+/**
+ * Input parmeters consist of test spec (defined in test_spec.h) to
+ * define the set of tests to run.
+ */
+#include "test_spec.h"
+
+/* Output parameters consist of a test summary followed
+ * by a setof [0..*] variable length test result records.
+ * Each test result has a fixed size structure followed
+ * by variable length parameters that specify the test
+ * name and group.
+ */
+#include "test_result.h"
+
+#endif /* TS_TEST_RUNNER_RUN_TESTS */
diff --git a/protocols/service/test_runner/packed-c/status.h b/protocols/service/test_runner/packed-c/status.h
new file mode 100644
index 0000000..d0a00e0
--- /dev/null
+++ b/protocols/service/test_runner/packed-c/status.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef TS_TEST_RUNNER_STATUS_H
+#define TS_TEST_RUNNER_STATUS_H
+
+/**
+ * Test runner service level statos codes
+ */
+enum
+{
+	/**
+	 * Returned if an operation completed successfully.
+	 * This doesn't mean that requested tests passed
+	 * but rather that the test runner operation
+	 * completed normally.
+	 */
+	TS_TEST_RUNNER_STATUS_SUCCESS = 0,
+
+	/**
+	 * Generic error occurred.
+	 */
+	TS_TEST_RUNNER_STATUS_ERROR = -1,
+
+	/**
+	 * Invalid test resuts returned by service provider.
+	 */
+	TS_TEST_RUNNER_STATUS_INVALID_TEST_RESULTS = -2
+};
+
+#endif /* TS_TEST_RUNNER_STATUS_H */
diff --git a/protocols/service/test_runner/packed-c/test_result.h b/protocols/service/test_runner/packed-c/test_result.h
new file mode 100644
index 0000000..888d2ec
--- /dev/null
+++ b/protocols/service/test_runner/packed-c/test_result.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef TS_TEST_RUNNER_TEST_RESULT
+#define TS_TEST_RUNNER_TEST_RESULT
+
+#include <stdint.h>
+
+/**
+ * Test result summary structure
+ */
+struct __attribute__ ((__packed__)) ts_test_runner_result_summary
+{
+  uint32_t num_tests;
+  uint32_t num_passed;
+  uint32_t num_failed;
+};
+
+/**
+ * Variable length parameter tag for a test result object.
+ * Multiple test results may be returned for a test run.
+ */
+enum
+{
+    /* A test result record describes the result of a
+     * particular test.
+     */
+    TS_TEST_RUNNER_TEST_RESULT_TAG = 1
+};
+
+/* Test run state values */
+enum
+{
+    TS_TEST_RUNNER_TEST_RESULT_RUN_STATE_NOT_RUN = 1,
+    TS_TEST_RUNNER_TEST_RESULT_RUN_STATE_PASSED = 2,
+    TS_TEST_RUNNER_TEST_RESULT_RUN_STATE_FAILED = 3
+};
+
+/* Test result fixed sized structure */
+struct __attribute__ ((__packed__)) ts_test_runner_test_result
+{
+  uint32_t run_state;
+  uint32_t fail_line;
+};
+
+/* Variable length output parameter tags */
+enum
+{
+    /* The name of the test */
+    TS_TEST_RUNNER_TEST_RESULT_TAG_NAME  = 1,
+
+    /* The group the test belongs to */
+    TS_TEST_RUNNER_TEST_RESULT_TAG_GROUP  = 2
+};
+
+#endif /* TS_TEST_RUNNER_TEST_RESULT */
diff --git a/protocols/service/test_runner/packed-c/test_spec.h b/protocols/service/test_runner/packed-c/test_spec.h
new file mode 100644
index 0000000..c31b367
--- /dev/null
+++ b/protocols/service/test_runner/packed-c/test_spec.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef TS_TEST_RUNNER_TEST_SPEC
+#define TS_TEST_RUNNER_TEST_SPEC
+
+/**
+ * Variable length parameters used to specify a test or
+ * group of tests.  A missing parameter is interpreted
+ * as a wildcard.
+ */
+enum
+{
+    /* Specifies the name of a particular test to run.
+     * The parameter should consist of an ascii string
+     * without a zero terminator.
+     */
+    TS_TEST_RUNNER_TEST_SPEC_TAG_NAME = 1,
+
+    /* Specifies a group of tests to run.
+     * The parameter should consist of an ascii string
+     * without a zero terminator.
+     */
+    TS_TEST_RUNNER_TEST_SPEC_TAG_GROUP = 2
+};
+
+#endif /* TS_TEST_RUNNER_TEST_SPEC */