Add HTTP based RPC support

To support test access to a DUT from a host PC, an RPC caller
is added that uses an HTTP PUT to carry RPC call requests,
handled by a REST API server running on the DUT. The
implementation uses libcurl which must be installed on the
host machine.

Signed-off-by: Julian Hall <julian.hall@arm.com>
Change-Id: I46a2720ce6281df0cb0de062e14e9090b4dcaaab
diff --git a/components/rpc/http/caller/http_caller.c b/components/rpc/http/caller/http_caller.c
new file mode 100644
index 0000000..af541e8
--- /dev/null
+++ b/components/rpc/http/caller/http_caller.c
@@ -0,0 +1,344 @@
+/*
+ * Copyright (c) 2023, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <trace.h>
+#include <curl/curl.h>
+#include <protocols/rpc/common/packed-c/status.h>
+#include <protocols/rpc/common/packed-c/header.h>
+#include "http_caller.h"
+
+#define USER_AGENT	"libcurl-agent/1.0"
+
+struct payload_buffer {
+	uint8_t *data;
+	size_t size;
+	size_t pos;
+};
+
+
+static rpc_call_handle call_begin(void *context, uint8_t **req_buf, size_t req_len);
+static rpc_status_t call_invoke(void *context, rpc_call_handle handle, uint32_t opcode,
+	rpc_opstatus_t *opstatus, uint8_t **resp_buf, size_t *resp_len);
+static void call_end(void *context, rpc_call_handle handle);
+
+static size_t request_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
+{
+	size_t bytes_requested = size * nmemb;
+	struct payload_buffer *buf = (struct payload_buffer *)userdata;
+
+	size_t bytes_remaining = buf->size - buf->pos;
+	size_t bytes_to_send = (bytes_remaining < bytes_requested) ?
+		bytes_remaining : bytes_requested;
+
+	memcpy(ptr, &buf->data[buf->pos], bytes_to_send);
+
+	buf->pos += bytes_to_send;
+
+	return bytes_to_send;
+}
+
+static size_t response_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
+{
+	size_t bytes_received = size * nmemb;
+	struct payload_buffer *buf = (struct payload_buffer *)userdata;
+
+	buf->data = realloc(buf->data, buf->size + bytes_received);
+
+	if (buf->data) {
+		memcpy(&buf->data[buf->size], ptr, bytes_received);
+		buf->size += bytes_received;
+	} else {
+		EMSG("Out of memory");
+		bytes_received = 0;
+	}
+
+	return bytes_received;
+}
+
+static bool send_head_request(const char *url,
+	struct payload_buffer *response_buf,
+	long *http_code)
+{
+	assert(url);
+	assert(response_buf);
+
+	bool is_success = false;
+	CURL *curl_session = curl_easy_init();
+
+	*http_code = 0;
+
+	if (curl_session) {
+
+		curl_easy_setopt(curl_session, CURLOPT_URL, url);
+		curl_easy_setopt(curl_session, CURLOPT_NOBODY, 1L);
+		curl_easy_setopt(curl_session, CURLOPT_WRITEFUNCTION, response_callback);
+		curl_easy_setopt(curl_session, CURLOPT_WRITEDATA, (void *)response_buf);
+		curl_easy_setopt(curl_session, CURLOPT_USERAGENT, USER_AGENT);
+
+		CURLcode status = curl_easy_perform(curl_session);
+
+		if (status == CURLE_OK) {
+
+			status = curl_easy_getinfo(curl_session, CURLINFO_RESPONSE_CODE, http_code);
+			is_success = (status == CURLE_OK) && (*http_code >= 200) && (*http_code < 300);
+		}
+
+		curl_easy_cleanup(curl_session);
+	} else {
+		EMSG("Failed to init Curl session");
+	}
+
+	return is_success;
+}
+
+static bool send_put_request(const char *url,
+	struct payload_buffer *request_buf,
+	struct payload_buffer *response_buf)
+{
+	bool is_success = false;
+
+	assert(url);
+	assert(request_buf);
+	assert(response_buf);
+
+	CURL *curl_session = curl_easy_init();
+
+	if (curl_session) {
+
+		curl_easy_setopt(curl_session, CURLOPT_UPLOAD, 1L);
+		curl_easy_setopt(curl_session, CURLOPT_URL, url);
+		curl_easy_setopt(curl_session, CURLOPT_READFUNCTION, request_callback);
+		curl_easy_setopt(curl_session, CURLOPT_READDATA, (void *)request_buf);
+		curl_easy_setopt(curl_session, CURLOPT_INFILESIZE_LARGE, (curl_off_t)request_buf->size);
+		curl_easy_setopt(curl_session, CURLOPT_WRITEFUNCTION, response_callback);
+		curl_easy_setopt(curl_session, CURLOPT_WRITEDATA, (void *)response_buf);
+		curl_easy_setopt(curl_session, CURLOPT_USERAGENT, USER_AGENT);
+
+		CURLcode status = curl_easy_perform(curl_session);
+
+		if (status == CURLE_OK) {
+
+			long http_code = 0;
+
+			status = curl_easy_getinfo(curl_session, CURLINFO_RESPONSE_CODE, &http_code);
+			is_success = (status == CURLE_OK) && (http_code >= 200) && (http_code < 300);
+		}
+
+		curl_easy_cleanup(curl_session);
+	} else {
+		EMSG("Failed to init Curl session");
+	}
+
+	return is_success;
+}
+
+static void prepare_call_url(const struct http_caller *s,
+	unsigned int opcode,
+	char *url_buf,
+	size_t url_buf_size)
+{
+	size_t base_url_len = strnlen(s->rpc_call_url, HTTP_CALLER_MAX_URL_LEN);
+
+	assert(base_url_len > 0);
+	assert(base_url_len < url_buf_size);
+
+	memset(url_buf, 0, url_buf_size);
+	memcpy(url_buf, s->rpc_call_url, base_url_len);
+
+	/* Ensure '/' is present before adding opcode */
+	if (url_buf[base_url_len - 1] != '/') {
+
+		url_buf[base_url_len] = '/';
+		base_url_len += 1;
+	}
+
+	size_t remaining_space = url_buf_size - base_url_len;
+	size_t opcode_len = snprintf(&url_buf[base_url_len],
+		remaining_space, "%u", opcode);
+
+	assert(opcode_len < remaining_space);
+}
+
+struct rpc_caller *http_caller_init(struct http_caller *s)
+{
+	assert(s);
+
+	struct rpc_caller *base = &s->rpc_caller;
+
+	rpc_caller_init(base, s);
+	base->call_begin = call_begin;
+	base->call_invoke = call_invoke;
+	base->call_end = call_end;
+
+	memset(s->rpc_call_url, 0, sizeof(s->rpc_call_url));
+
+	s->req_body_size = 0;
+	s->req_body_buf = NULL;
+	s->resp_body_buf = NULL;
+
+	CURLcode status = curl_global_init(CURL_GLOBAL_ALL);
+
+	return (status == CURLE_OK) ? base : NULL;
+}
+
+void http_caller_deinit(struct http_caller *s)
+{
+	assert(s);
+
+	s->rpc_caller.context = NULL;
+	s->rpc_caller.call_begin = NULL;
+	s->rpc_caller.call_invoke = NULL;
+	s->rpc_caller.call_end = NULL;
+
+	call_end(s, s);
+}
+
+bool http_caller_probe(const char *url, long *http_code)
+{
+	assert(url);
+
+	struct payload_buffer response_buf;
+
+	response_buf.data = NULL;
+	response_buf.size = 0;
+
+	bool is_reached = send_head_request(url, &response_buf, http_code);
+
+	free(response_buf.data);
+
+	return is_reached;
+}
+
+int http_caller_open(struct http_caller *s, const char *rpc_call_url)
+{
+	assert(s);
+	assert(rpc_call_url);
+
+	strncpy(s->rpc_call_url, rpc_call_url, sizeof(s->rpc_call_url));
+
+	return 0;
+}
+
+int http_caller_close(struct http_caller *s)
+{
+	(void)s;
+	return 0;
+}
+
+static rpc_call_handle call_begin(void *context,
+	uint8_t **req_buf,
+	size_t req_len)
+{
+	assert(context);
+	assert(req_buf || !req_len);
+
+	rpc_call_handle handle = NULL;
+	struct http_caller *s = (struct http_caller *)context;
+
+	if (!s->req_body_buf) {
+
+		size_t req_body_size = sizeof(struct ts_rpc_req_hdr) + req_len;
+
+		s->req_body_buf = malloc(req_body_size);
+
+		if (s->req_body_buf) {
+
+			memset(s->req_body_buf, 0, req_body_size);
+
+			handle = s;
+			*req_buf = &s->req_body_buf[sizeof(struct ts_rpc_req_hdr)];
+			s->req_body_size = req_body_size;
+
+			struct ts_rpc_req_hdr *rpc_hdr = (struct ts_rpc_req_hdr *)s->req_body_buf;
+
+			rpc_hdr->encoding = s->rpc_caller.encoding;
+			rpc_hdr->param_len = req_len;
+
+		} else {
+			EMSG("Out of memory");
+		}
+	}
+
+	return handle;
+}
+
+static rpc_status_t call_invoke(void *context,
+	rpc_call_handle handle,
+	uint32_t opcode,
+	rpc_opstatus_t *opstatus,
+	uint8_t **resp_buf,
+	size_t *resp_len)
+{
+	rpc_status_t rpc_status = TS_RPC_ERROR_INTERNAL;
+	struct http_caller *s = (struct http_caller *)context;
+
+	*resp_len = 0;
+
+	if ((handle == s) && s->req_body_buf) {
+
+		struct ts_rpc_req_hdr *rpc_hdr = (struct ts_rpc_req_hdr *)s->req_body_buf;
+		struct payload_buffer request_buf = {0};
+		struct payload_buffer response_buf = {0};
+
+		rpc_status = TS_RPC_ERROR_EP_DOES_NOT_EXIT;
+
+		rpc_hdr->opcode = opcode;
+
+		request_buf.data = s->req_body_buf;
+		request_buf.size = s->req_body_size;
+
+		char call_url[HTTP_CALLER_MAX_URL_LEN];
+
+		prepare_call_url(s, opcode, call_url, sizeof(call_url));
+
+		if (send_put_request(call_url, &request_buf, &response_buf) &&
+			response_buf.data &&
+			response_buf.size >= sizeof(struct ts_rpc_resp_hdr)) {
+
+			struct ts_rpc_resp_hdr *resp_hdr = (struct ts_rpc_resp_hdr *)response_buf.data;
+			size_t response_param_len = response_buf.size - sizeof(struct ts_rpc_resp_hdr);
+
+			if (resp_hdr->param_len == response_param_len) {
+
+				rpc_status = resp_hdr->rpc_status;
+
+				if (rpc_status == TS_RPC_CALL_ACCEPTED) {
+
+					*resp_buf = &response_buf.data[sizeof(struct ts_rpc_resp_hdr)];
+					*resp_len = response_param_len;
+
+					*opstatus = resp_hdr->op_status;
+				}
+
+			} else {
+
+				rpc_status = TS_RPC_ERROR_INVALID_RESP_BODY;
+			}
+		}
+	}
+
+	return rpc_status;
+}
+
+static void call_end(void *context, rpc_call_handle handle)
+{
+	struct http_caller *s = (struct http_caller *)context;
+
+	if ((handle == s) && s->req_body_buf) {
+
+		free(s->req_body_buf);
+		s->req_body_buf = NULL;
+
+		free(s->resp_body_buf);
+		s->resp_body_buf = NULL;
+	}
+}