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