/* * Copyright (c) 2023 Egor Tensin * 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 #include #include #include 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, ¶ms); 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, ¶ms); 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, ¶ms); 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, ¶ms); 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; }