Add generalised service provider

Adds common service provider that receives call requests and delegates
them to the approprate handle provided by a concrete service provider.

Change-Id: I4f0b913938b4ecc9710f57c99025388262c145d7
Signed-off-by: Julian Hall <julian.hall@arm.com>
diff --git a/components/service/common/provider/component.cmake b/components/service/common/provider/component.cmake
new file mode 100644
index 0000000..86bdf49
--- /dev/null
+++ b/components/service/common/provider/component.cmake
@@ -0,0 +1,19 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2020, 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}/service_provider.c"
+	)
+
+
+target_include_directories(${TGT}
+	 PRIVATE
+		"${CMAKE_CURRENT_LIST_DIR}"
+	)
diff --git a/components/service/common/provider/service_provider.c b/components/service/common/provider/service_provider.c
new file mode 100644
index 0000000..17f742f
--- /dev/null
+++ b/components/service/common/provider/service_provider.c
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2020, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include "service_provider.h"
+#include <protocols/rpc/common/packed-c/status.h>
+#include <stddef.h>
+
+static const struct service_handler *find_handler(const struct service_provider *sp,
+						      uint32_t opcode)
+{
+	const struct service_handler *handler = NULL;
+	size_t index = 0;
+
+	if (sp->num_handlers) {
+		while (index < sp->num_handlers) {
+			if (service_handler_get_opcode(&sp->handlers[index]) == opcode) {
+				handler = &sp->handlers[index];
+				break;
+			}
+			++index;
+		}
+	}
+
+	return handler;
+}
+
+static rpc_status_t receive(struct call_ep *base_ep, struct call_req *req)
+{
+	rpc_status_t rpc_status;
+	struct service_provider *sp = NULL;
+	const struct service_handler *handler = NULL;
+
+	sp = (struct service_provider*)((char*)base_ep - offsetof(struct service_provider, base));
+	handler = find_handler(sp, call_req_get_opcode(req));
+
+    if (handler) {
+
+        req->serializer = sp->default_serializer;
+        rpc_status = service_handler_invoke(handler, base_ep->context, req);
+    }
+    else {
+
+        rpc_status = TS_RPC_ERROR_INVALID_OPCODE;
+    }
+
+	return rpc_status;
+}
+
+void service_provider_init(struct service_provider *sp, void *context,
+			     const struct service_handler *handlers,
+			     size_t num_handlers)
+{
+	sp->base.receive = receive;
+	sp->base.context = context;
+
+	sp->handlers = handlers;
+	sp->num_handlers = num_handlers;
+
+	sp->default_serializer = NULL;
+}
diff --git a/components/service/common/provider/service_provider.h b/components/service/common/provider/service_provider.h
new file mode 100644
index 0000000..6f86073
--- /dev/null
+++ b/components/service/common/provider/service_provider.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2020, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef SERVICE_PROVIDER_H
+#define SERVICE_PROVIDER_H
+
+#include <rpc/common/endpoint/call_ep.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \brief Service handler
+ *
+ * Defines a mapping between an opcode and a handler function. A complete
+ * service interface is defined by an array of service request handlers.
+ */
+struct service_handler {
+	uint32_t opcode;
+	rpc_status_t (*invoke)(void *context, struct call_req* req);
+};
+
+static inline int service_handler_invoke(const struct service_handler *handler,
+						  void *context, struct call_req* req)
+{
+	return handler->invoke(context, req);
+}
+
+static inline uint32_t service_handler_get_opcode(const struct service_handler *handler)
+{
+	return handler->opcode;
+}
+
+/** \brief Service provider
+ *
+ * A generalised service provider that acts as an rpc call endpoint.  It receives call
+ * requests and delegates them to the approprate handle provided by a concrete service
+ * provider.
+ */
+struct service_provider {
+    struct call_ep base;
+    const struct service_handler *handlers;
+    size_t num_handlers;
+    call_param_serializer_ptr default_serializer;
+};
+
+static inline struct call_ep *service_provider_get_call_ep(struct service_provider *sp)
+{
+	return &sp->base;
+}
+
+void service_provider_init(struct service_provider *sp, void *context,
+			     	const struct service_handler *handlers,
+			     	size_t num_handlers);
+
+static inline void service_set_default_serializer(struct service_provider *sp,
+    				call_param_serializer_ptr serializer)
+{
+    sp->default_serializer = serializer;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SERVICE_PROVIDER_H */
diff --git a/components/service/common/provider/test/component.cmake b/components/service/common/provider/test/component.cmake
new file mode 100644
index 0000000..74633a3
--- /dev/null
+++ b/components/service/common/provider/test/component.cmake
@@ -0,0 +1,14 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2020, 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}/service_framework_tests.cpp"
+	)
+
diff --git a/components/service/common/provider/test/service_framework_tests.cpp b/components/service/common/provider/test/service_framework_tests.cpp
new file mode 100644
index 0000000..4aa3fdc
--- /dev/null
+++ b/components/service/common/provider/test/service_framework_tests.cpp
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2020, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <string>
+#include <cstring>
+#include <service/common/provider/service_provider.h>
+#include <protocols/rpc/common/packed-c/status.h>
+#include <rpc/direct/direct_caller.h>
+#include <CppUTest/TestHarness.h>
+
+
+TEST_GROUP(ServiceFrameworkTests)
+{
+    static rpc_status_t handlerThatSucceeds(void *context, struct call_req* req)
+    {
+        (void)context;
+
+        struct call_param_buf *respBuf = call_req_get_resp_buf(req);
+
+        std::string responseString("Yay!");
+        respBuf->data_len = responseString.copy((char*)respBuf->data, respBuf->size);
+
+        call_req_set_opstatus(req, SERVICE_SPECIFIC_SUCCESS_CODE);
+
+        return TS_RPC_CALL_ACCEPTED;
+    }
+
+    static rpc_status_t handlerThatFails(void *context, struct call_req* req)
+    {
+        (void)context;
+
+        struct call_param_buf *respBuf = call_req_get_resp_buf(req);
+
+        std::string responseString("Ehh!");
+        respBuf->data_len = responseString.copy((char*)respBuf->data, respBuf->size);
+
+        call_req_set_opstatus(req, SERVICE_SPECIFIC_ERROR_CODE);
+
+        return TS_RPC_CALL_ACCEPTED;
+    }
+
+    void setup()
+    {
+        memset(&m_direct_caller, sizeof(m_direct_caller), 0);
+    }
+
+    void teardown()
+    {
+        direct_caller_deinit(&m_direct_caller);
+    }
+
+    static const uint32_t SOME_ARBITRARY_OPCODE = 666;
+    static const uint32_t ANOTHER_ARBITRARY_OPCODE = 901;
+    static const uint32_t YET_ANOTHER_ARBITRARY_OPCODE = 7;
+    static const int SERVICE_SPECIFIC_ERROR_CODE = 101;
+    static const int SERVICE_SPECIFIC_SUCCESS_CODE = 100;
+
+    struct direct_caller m_direct_caller;
+};
+
+TEST(ServiceFrameworkTests, serviceWithNoOps)
+{
+    /* Constructs a service endpoint with no handlers */
+    struct service_provider ep;
+
+    service_provider_init(&ep, &ep, NULL, 0);
+    struct rpc_caller *caller = direct_caller_init_default(&m_direct_caller,
+                                            service_provider_get_call_ep(&ep));
+
+    rpc_call_handle handle;
+    uint8_t *req_buf;
+    uint8_t *resp_buf;
+    size_t req_len = 100;
+    size_t resp_len;
+    int opstatus;
+
+    handle = rpc_caller_begin(caller, &req_buf, req_len);
+    CHECK(handle);
+
+    rpc_status_t rpc_status = rpc_caller_invoke(caller, handle, SOME_ARBITRARY_OPCODE,
+                                    &opstatus, &resp_buf, &resp_len);
+
+    rpc_caller_end(caller, handle);
+
+    CHECK_EQUAL(TS_RPC_ERROR_INVALID_OPCODE, rpc_status);
+}
+
+TEST(ServiceFrameworkTests, serviceWithOps)
+{
+    /* Constructs a service endpoint with a couple of handlers */
+    struct service_handler handlers[2];
+    handlers[0].opcode = SOME_ARBITRARY_OPCODE;
+    handlers[0].invoke = handlerThatSucceeds;
+    handlers[1].opcode = ANOTHER_ARBITRARY_OPCODE;
+    handlers[1].invoke = handlerThatFails;
+
+    struct service_provider ep;
+
+    service_provider_init(&ep, &ep, handlers, 2);
+    struct rpc_caller *caller = direct_caller_init_default(&m_direct_caller,
+                                            service_provider_get_call_ep(&ep));
+
+    rpc_call_handle handle;
+    rpc_status_t rpc_status;
+    uint8_t *req_buf;
+    uint8_t *resp_buf;
+    size_t req_len = 100;
+    size_t resp_len;
+    int opstatus;
+    std::string respString;
+
+    /* Expect this call transaction to succeed */
+    handle = rpc_caller_begin(caller, &req_buf, req_len);
+    CHECK(handle);
+
+    rpc_status = rpc_caller_invoke(caller, handle, SOME_ARBITRARY_OPCODE,
+                                    &opstatus, &resp_buf, &resp_len);
+
+    respString = std::string((const char*)resp_buf, resp_len);
+
+    rpc_caller_end(caller, handle);
+
+    CHECK_EQUAL(TS_RPC_CALL_ACCEPTED, rpc_status);
+    CHECK_EQUAL(SERVICE_SPECIFIC_SUCCESS_CODE, opstatus);
+    CHECK(respString == "Yay!");
+
+    /* Expect this call transaction to fail */
+    handle = rpc_caller_begin(caller, &req_buf, req_len);
+    CHECK(handle);
+
+    rpc_status = rpc_caller_invoke(caller, handle, ANOTHER_ARBITRARY_OPCODE,
+        &opstatus, &resp_buf, &resp_len);
+
+    respString = std::string((const char*)resp_buf, resp_len);
+
+    rpc_caller_end(caller, handle);
+
+    CHECK_EQUAL(TS_RPC_CALL_ACCEPTED, rpc_status);
+    CHECK_EQUAL(SERVICE_SPECIFIC_ERROR_CODE, opstatus);
+    CHECK(respString == "Ehh!");
+
+    /* Try an unsupported opcode */
+    handle = rpc_caller_begin(caller, &req_buf, req_len);
+    CHECK(handle);
+
+    rpc_status = rpc_caller_invoke(caller, handle, YET_ANOTHER_ARBITRARY_OPCODE,
+        &opstatus, &resp_buf, &resp_len);
+
+    rpc_caller_end(caller, handle);
+
+    CHECK_EQUAL(TS_RPC_ERROR_INVALID_OPCODE, rpc_status);
+}