aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src/json_rpc.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/json_rpc.c')
-rw-r--r--src/json_rpc.c587
1 files changed, 587 insertions, 0 deletions
diff --git a/src/json_rpc.c b/src/json_rpc.c
new file mode 100644
index 0000000..538cff0
--- /dev/null
+++ b/src/json_rpc.c
@@ -0,0 +1,587 @@
+/*
+ * Copyright (c) 2023 Egor Tensin <Egor.Tensin@gmail.com>
+ * This file is part of the "cimple" project.
+ * For details, see https://github.com/egor-tensin/cimple.
+ * Distributed under the MIT License.
+ */
+
+#include "json_rpc.h"
+#include "json.h"
+#include "log.h"
+
+#include <json-c/json_object.h>
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct jsonrpc_request {
+ struct json_object *impl;
+};
+
+struct jsonrpc_response {
+ struct json_object *impl;
+};
+
+static const char *const jsonrpc_key_version = "jsonrpc";
+static const char *const jsonrpc_key_id = "id";
+static const char *const jsonrpc_key_method = "method";
+static const char *const jsonrpc_key_params = "params";
+
+static const char *const jsonrpc_value_version = "2.0";
+
+static int jsonrpc_check_version(struct json_object *obj)
+{
+ const char *key = jsonrpc_key_version;
+ const char *version = NULL;
+
+ int ret = json_get_string(obj, key, &version);
+ if (ret < 0)
+ return ret;
+
+ if (strcmp(version, jsonrpc_value_version)) {
+ log_err("JSON-RPC: invalid '%s' value: %s\n", key, version);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int jsonrpc_set_version(struct json_object *obj)
+{
+ return json_set_string_const_key(obj, jsonrpc_key_version, jsonrpc_value_version);
+}
+
+static int jsonrpc_check_id_type(struct json_object *id)
+{
+ if (!json_object_is_type(id, json_type_string) && !json_object_is_type(id, json_type_int)) {
+ log_err("JSON-RPC: key '%s' must be either an integer or a string\n",
+ jsonrpc_key_id);
+ return -1;
+ }
+ return 0;
+}
+
+static int jsonrpc_check_id(struct json_object *obj, int required)
+{
+ const char *key = jsonrpc_key_id;
+
+ if (!json_has(obj, key)) {
+ if (!required)
+ return 0;
+ log_err("JSON-RPC: key is missing: %s\n", key);
+ return -1;
+ }
+
+ struct json_object *id = NULL;
+
+ int ret = json_get(obj, key, &id);
+ if (ret < 0)
+ return ret;
+ return jsonrpc_check_id_type(id);
+}
+
+static int jsonrpc_set_id(struct json_object *obj, int id)
+{
+ return json_set_int_const_key(obj, jsonrpc_key_id, id);
+}
+
+static int jsonrpc_check_method(struct json_object *obj)
+{
+ const char *key = jsonrpc_key_method;
+ const char *method = NULL;
+ return json_get_string(obj, key, &method);
+}
+
+static int jsonrpc_set_method(struct json_object *obj, const char *method)
+{
+ return json_set_string_const_key(obj, jsonrpc_key_method, method);
+}
+
+static int jsonrpc_check_params_type(struct json_object *params)
+{
+ if (!json_object_is_type(params, json_type_object) &&
+ !json_object_is_type(params, json_type_array)) {
+ log_err("JSON-RPC: key '%s' must be either an object or an array\n",
+ jsonrpc_key_params);
+ return -1;
+ }
+ return 0;
+}
+
+static int jsonrpc_check_params(struct json_object *obj)
+{
+ const char *key = jsonrpc_key_params;
+
+ if (!json_has(obj, key))
+ return 0;
+
+ struct json_object *params = NULL;
+
+ int ret = json_get(obj, key, &params);
+ if (ret < 0)
+ return ret;
+ return jsonrpc_check_params_type(params);
+}
+
+static int jsonrpc_set_params(struct json_object *obj, struct json_object *params)
+{
+ const char *key = jsonrpc_key_params;
+
+ int ret = jsonrpc_check_params_type(params);
+ if (ret < 0)
+ return ret;
+ return json_set_const_key(obj, key, params);
+}
+
+static const char *const jsonrpc_key_result = "result";
+static const char *const jsonrpc_key_error = "error";
+
+static const char *const jsonrpc_key_code = "code";
+static const char *const jsonrpc_key_message = "message";
+
+static int jsonrpc_check_error(struct json_object *obj)
+{
+ const char *key = jsonrpc_key_error;
+ struct json_object *error = NULL;
+
+ int ret = json_get(obj, key, &error);
+ if (ret < 0)
+ return ret;
+
+ int64_t code = -1;
+
+ ret = json_get_int(error, jsonrpc_key_code, &code);
+ if (ret < 0) {
+ log_err("JSON-RPC: key is missing or not an integer: %s\n", jsonrpc_key_code);
+ return -1;
+ }
+
+ const char *message = NULL;
+
+ ret = json_get_string(error, jsonrpc_key_message, &message);
+ if (ret < 0) {
+ log_err("JSON-RPC: key is missing or not a string: %s\n", jsonrpc_key_message);
+ return -1;
+ }
+
+ return ret;
+}
+
+static int jsonrpc_check_result_or_error(struct json_object *obj)
+{
+ const char *key_result = jsonrpc_key_result;
+ const char *key_error = jsonrpc_key_error;
+
+ if (!json_has(obj, key_result) && !json_has(obj, key_error)) {
+ log_err("JSON-RPC: either '%s' or '%s' must be present\n", key_result, key_error);
+ return -1;
+ }
+
+ if (json_has(obj, key_result))
+ return 0;
+
+ return jsonrpc_check_error(obj);
+}
+
+static int jsonrpc_request_create_internal(struct jsonrpc_request **_request, int *id,
+ const char *method, struct json_object *params)
+{
+ int ret = 0;
+
+ struct jsonrpc_request *request = malloc(sizeof(struct jsonrpc_request));
+ if (!request) {
+ log_errno("malloc");
+ ret = -1;
+ goto exit;
+ }
+
+ request->impl = json_object_new_object();
+ if (!request->impl) {
+ json_errno("json_object_new_object");
+ ret = -1;
+ goto free;
+ }
+
+ ret = jsonrpc_set_version(request->impl);
+ if (ret < 0)
+ goto free_impl;
+
+ if (id) {
+ ret = jsonrpc_set_id(request->impl, *id);
+ if (ret < 0)
+ goto free_impl;
+ }
+
+ ret = jsonrpc_set_method(request->impl, method);
+ if (ret < 0)
+ goto free_impl;
+
+ if (params) {
+ ret = jsonrpc_set_params(request->impl, params);
+ if (ret < 0)
+ goto free_impl;
+ }
+
+ *_request = request;
+ goto exit;
+
+free_impl:
+ json_object_put(request->impl);
+free:
+ free(request);
+exit:
+ return ret;
+}
+
+int jsonrpc_request_create(struct jsonrpc_request **_request, int id, const char *method,
+ struct json_object *params)
+{
+ return jsonrpc_request_create_internal(_request, &id, method, params);
+}
+
+void jsonrpc_request_destroy(struct jsonrpc_request *request)
+{
+ json_object_put(request->impl);
+ free(request);
+}
+
+int jsonrpc_notification_create(struct jsonrpc_request **_request, const char *method,
+ struct json_object *params)
+{
+ return jsonrpc_request_create_internal(_request, NULL, method, params);
+}
+
+int jsonrpc_request_is_notification(const struct jsonrpc_request *request)
+{
+ return !json_has(request->impl, jsonrpc_key_id);
+}
+
+const char *jsonrpc_request_to_string(struct jsonrpc_request *request)
+{
+ return json_to_string(request->impl);
+}
+
+static int jsonrpc_request_from_json(struct jsonrpc_request **_request, struct json_object *impl)
+{
+ int ret = 0;
+
+ ret = jsonrpc_check_version(impl);
+ if (ret < 0)
+ return ret;
+ ret = jsonrpc_check_id(impl, 0);
+ if (ret < 0)
+ return ret;
+ ret = jsonrpc_check_method(impl);
+ if (ret < 0)
+ return ret;
+ ret = jsonrpc_check_params(impl);
+ if (ret < 0)
+ return ret;
+
+ struct jsonrpc_request *request = malloc(sizeof(struct jsonrpc_request));
+ if (!request) {
+ log_errno("malloc");
+ return -1;
+ }
+ request->impl = impl;
+
+ *_request = request;
+ return ret;
+}
+
+int jsonrpc_request_parse(struct jsonrpc_request **_request, const char *src)
+{
+ struct json_object *impl = json_from_string(src);
+ if (!impl) {
+ log_err("JSON-RPC: failed to parse request\n");
+ return -1;
+ }
+
+ int ret = jsonrpc_request_from_json(_request, impl);
+ if (ret < 0)
+ goto free_impl;
+
+ return ret;
+
+free_impl:
+ json_object_put(impl);
+
+ return ret;
+}
+
+int jsonrpc_request_send(const struct jsonrpc_request *request, int fd)
+{
+ return json_send(request->impl, fd);
+}
+
+int jsonrpc_request_recv(struct jsonrpc_request **request, int fd)
+{
+ struct json_object *impl = json_recv(fd);
+ if (!impl) {
+ log_err("JSON-RPC: failed to receive request\n");
+ return -1;
+ }
+
+ int ret = jsonrpc_request_from_json(request, impl);
+ if (ret < 0)
+ goto free_impl;
+
+ return ret;
+
+free_impl:
+ json_object_put(impl);
+
+ return ret;
+}
+
+const char *jsonrpc_request_get_method(const struct jsonrpc_request *request)
+{
+ const char *method = NULL;
+ int ret = json_get_string(request->impl, jsonrpc_key_method, &method);
+ if (ret < 0) {
+ /* Should never happen. */
+ return NULL;
+ }
+ return method;
+}
+
+static struct json_object *jsonrpc_request_create_params(struct jsonrpc_request *request)
+{
+ const char *const key = jsonrpc_key_params;
+
+ if (!json_has(request->impl, key)) {
+ struct json_object *params = json_object_new_object();
+ if (!params) {
+ json_errno("json_object_new_object");
+ return NULL;
+ }
+ int ret = json_set(request->impl, key, params);
+ if (ret < 0) {
+ json_object_put(params);
+ return NULL;
+ }
+ return params;
+ }
+
+ struct json_object *params = NULL;
+ int ret = json_get(request->impl, key, &params);
+ if (ret < 0)
+ return NULL;
+ return params;
+}
+
+int jsonrpc_request_get_param_string(const struct jsonrpc_request *request, const char *name,
+ const char **value)
+{
+ struct json_object *params = NULL;
+ int ret = json_get(request->impl, jsonrpc_key_params, &params);
+ if (ret < 0)
+ return ret;
+ return json_get_string(params, name, value);
+}
+
+int jsonrpc_request_set_param_string(struct jsonrpc_request *request, const char *name,
+ const char *value)
+{
+ struct json_object *params = jsonrpc_request_create_params(request);
+ if (!params)
+ return -1;
+ return json_set_string(params, name, value);
+}
+
+int jsonrpc_request_get_param_int(const struct jsonrpc_request *request, const char *name,
+ int64_t *value)
+{
+ struct json_object *params = NULL;
+ int ret = json_get(request->impl, jsonrpc_key_params, &params);
+ if (ret < 0)
+ return ret;
+ return json_get_int(params, name, value);
+}
+
+int jsonrpc_request_set_param_int(struct jsonrpc_request *request, const char *name, int64_t value)
+{
+ struct json_object *params = jsonrpc_request_create_params(request);
+ if (!params)
+ return -1;
+ return json_set_int(params, name, value);
+}
+
+int jsonrpc_response_create_internal(struct jsonrpc_response **_response,
+ const struct jsonrpc_request *request,
+ struct json_object *result, struct json_object *error)
+{
+ int ret = 0;
+
+ struct jsonrpc_response *response = malloc(sizeof(struct jsonrpc_response));
+ if (!response) {
+ log_errno("malloc");
+ ret = -1;
+ goto exit;
+ }
+
+ response->impl = json_object_new_object();
+ if (!response->impl) {
+ json_errno("json_object_new_object");
+ ret = -1;
+ goto free;
+ }
+
+ ret = jsonrpc_set_version(response->impl);
+ if (ret < 0)
+ goto free_impl;
+
+ struct json_object *id = NULL;
+ ret = json_clone(request->impl, jsonrpc_key_id, &id);
+ if (ret < 0)
+ goto free_impl;
+
+ ret = json_set(response->impl, jsonrpc_key_id, id);
+ if (ret < 0) {
+ json_object_put(id);
+ goto free_impl;
+ }
+
+ if (error) {
+ ret = json_set_const_key(response->impl, jsonrpc_key_error, error);
+ if (ret < 0)
+ goto free_impl;
+ } else {
+ ret = json_set_const_key(response->impl, jsonrpc_key_result, result);
+ if (ret < 0)
+ goto free_impl;
+ }
+
+ *_response = response;
+ goto exit;
+
+free_impl:
+ json_object_put(response->impl);
+free:
+ free(response);
+exit:
+ return ret;
+}
+
+int jsonrpc_response_create(struct jsonrpc_response **response,
+ const struct jsonrpc_request *request, struct json_object *result)
+{
+ return jsonrpc_response_create_internal(response, request, result, NULL);
+}
+
+void jsonrpc_response_destroy(struct jsonrpc_response *response)
+{
+ json_object_put(response->impl);
+ free(response);
+}
+
+int jsonrpc_error_create(struct jsonrpc_response **response, struct jsonrpc_request *request,
+ int code, const char *message)
+{
+ struct json_object *error = json_object_new_object();
+ if (!error) {
+ json_errno("json_object_new_object");
+ return -1;
+ }
+
+ int ret = 0;
+
+ ret = json_set_int_const_key(error, jsonrpc_key_code, code);
+ if (ret < 0)
+ goto free;
+ ret = json_set_string_const_key(error, jsonrpc_key_message, message);
+ if (ret < 0)
+ goto free;
+
+ ret = jsonrpc_response_create_internal(response, request, NULL, error);
+ if (ret < 0)
+ goto free;
+
+ return ret;
+
+free:
+ json_object_put(error);
+
+ return ret;
+}
+
+int jsonrpc_response_is_error(const struct jsonrpc_response *response)
+{
+ return json_has(response->impl, jsonrpc_key_error);
+}
+
+const char *jsonrpc_response_to_string(struct jsonrpc_response *response)
+{
+ return json_to_string(response->impl);
+}
+
+static int jsonrpc_response_from_json(struct jsonrpc_response **_response, struct json_object *impl)
+{
+ int ret = 0;
+
+ ret = jsonrpc_check_version(impl);
+ if (ret < 0)
+ return ret;
+ ret = jsonrpc_check_id(impl, 1);
+ if (ret < 0)
+ return ret;
+ ret = jsonrpc_check_result_or_error(impl);
+ if (ret < 0)
+ return ret;
+
+ struct jsonrpc_response *response = malloc(sizeof(struct jsonrpc_response));
+ if (!response) {
+ log_errno("malloc");
+ return -1;
+ }
+ response->impl = impl;
+
+ *_response = response;
+ return ret;
+}
+
+int jsonrpc_response_parse(struct jsonrpc_response **_response, const char *src)
+{
+ struct json_object *impl = json_from_string(src);
+ if (!impl) {
+ log_err("JSON-RPC: failed to parse response\n");
+ return -1;
+ }
+
+ int ret = jsonrpc_response_from_json(_response, impl);
+ if (ret < 0)
+ goto free_impl;
+
+ return ret;
+
+free_impl:
+ json_object_put(impl);
+
+ return ret;
+}
+
+int jsonrpc_response_send(const struct jsonrpc_response *response, int fd)
+{
+ return json_send(response->impl, fd);
+}
+
+int jsonrpc_response_recv(struct jsonrpc_response **response, int fd)
+{
+ struct json_object *impl = json_recv(fd);
+ if (!impl) {
+ log_err("JSON-RPC: failed to receive response\n");
+ return -1;
+ }
+
+ int ret = jsonrpc_response_from_json(response, impl);
+ if (ret < 0)
+ goto free_impl;
+
+ return ret;
+
+free_impl:
+ json_object_put(impl);
+
+ return ret;
+}