From 0154b7de1306ba4f09b6e615d245a9a5b91c26c4 Mon Sep 17 00:00:00 2001 From: Diego Nehab <1635557+diegonehab@users.noreply.github.com> Date: Mon, 2 Dec 2024 14:07:53 +0000 Subject: [PATCH] refactor(!): simplify local/remote machine APIs --- src/Makefile | 4 +- src/asio-config.h | 4 + src/cartesi-machine.lua | 156 ++- src/clua-cartesi-jsonrpc.cpp | 186 ++- src/clua-cartesi.cpp | 48 +- src/clua-i-virtual-machine.cpp | 730 +++++++++- src/clua-i-virtual-machine.h | 168 ++- src/clua-jsonrpc-machine.cpp | 332 ----- src/clua-jsonrpc-machine.h | 41 - src/clua-machine-util.cpp | 514 -------- src/clua-machine-util.h | 194 --- src/clua-machine.cpp | 151 --- src/clua.h | 1 + src/i-virtual-machine.h | 96 +- src/json-util.h | 3 +- src/jsonrpc-connection.h | 72 - src/jsonrpc-discover.json | 1173 ++++++++--------- src/{clua-machine.h => jsonrpc-fork-result.h} | 26 +- src/jsonrpc-machine-c-api.cpp | 461 ++----- src/jsonrpc-machine-c-api.h | 283 ++-- src/jsonrpc-remote-machine.cpp | 96 +- src/jsonrpc-virtual-machine.cpp | 516 +++++--- src/jsonrpc-virtual-machine.h | 101 +- src/machine-c-api.cpp | 325 +++-- src/machine-c-api.h | 214 +-- src/machine.cpp | 19 + src/machine.h | 14 +- src/os.cpp | 8 +- src/os.h | 8 +- src/virtual-machine.cpp | 63 +- src/virtual-machine.h | 32 +- tests/lua/cartesi-machine-tests.lua | 57 +- tests/lua/cmio-test.lua | 28 +- tests/lua/log-with-mtime-transition.lua | 2 +- tests/lua/machine-bind.lua | 170 +-- tests/lua/machine-test.lua | 34 +- tests/lua/test-jsonrpc-fork.lua | 17 +- tests/misc/test-machine-c-api.cpp | 109 +- tests/scripts/test-cmio.sh | 23 +- tests/scripts/test-jsonrpc-server.sh | 12 +- 40 files changed, 3109 insertions(+), 3382 deletions(-) delete mode 100644 src/clua-jsonrpc-machine.cpp delete mode 100644 src/clua-jsonrpc-machine.h delete mode 100644 src/clua-machine-util.cpp delete mode 100644 src/clua-machine-util.h delete mode 100644 src/clua-machine.cpp delete mode 100644 src/jsonrpc-connection.h rename src/{clua-machine.h => jsonrpc-fork-result.h} (63%) diff --git a/src/Makefile b/src/Makefile index 348c2cd8c..e293c3c5c 100644 --- a/src/Makefile +++ b/src/Makefile @@ -372,14 +372,12 @@ LIBCARTESI_OBJS:= \ CARTESI_CLUA_OBJS:= \ clua.o \ clua-i-virtual-machine.o \ - clua-machine-util.o \ uarch-pristine-ram.o \ uarch-pristine-state-hash.o \ uarch-pristine-hash.o LUACARTESI_OBJS:= \ clua-cartesi.o \ - clua-machine.o \ $(CARTESI_CLUA_OBJS) LIBCARTESI_MERKLE_TREE_OBJS:= \ @@ -395,6 +393,7 @@ MERKLE_TREE_HASH_OBJS:= \ LIBCARTESI_JSONRPC_OBJS:= \ jsonrpc-virtual-machine.o \ + os.o \ jsonrpc-machine-c-api.o \ uarch-pristine-ram.o \ uarch-pristine-state-hash.o \ @@ -402,7 +401,6 @@ LIBCARTESI_JSONRPC_OBJS:= \ LUACARTESI_JSONRPC_OBJS:= \ clua-cartesi-jsonrpc.o \ - clua-jsonrpc-machine.o \ $(CARTESI_CLUA_OBJS) JSONRPC_REMOTE_CARTESI_MACHINE_OBJS:= \ diff --git a/src/asio-config.h b/src/asio-config.h index 4604238c9..ccb8aadbb 100644 --- a/src/asio-config.h +++ b/src/asio-config.h @@ -23,4 +23,8 @@ #define BOOST_ASIO_DISABLE_EPOLL #define BOOST_ASIO_DISABLE_EVENTFD +#ifdef NDEBUG +#define BOOST_ASIO_DISABLE_ERROR_LOCATION +#endif + #endif diff --git a/src/cartesi-machine.lua b/src/cartesi-machine.lua index 0a97d5490..1603748b6 100755 --- a/src/cartesi-machine.lua +++ b/src/cartesi-machine.lua @@ -47,10 +47,13 @@ where options are: --version-json display cartesi machine semantic version and exit. - --remote-address=
- use a remote cartesi machine listening to
instead of + --remote-address=: + use a remote cartesi machine listening to : instead of running a local cartesi machine. + --remote-health-check + checks health of remote server and exit + --remote-fork fork the remote cartesi machine before the execution. @@ -509,13 +512,13 @@ where options are: --append-entrypoint-file= like --append-entrypoint, but read contents from a file. - --gdb[=
] - listen at
and wait for a GDB connection to debug the machine. - if
is omitted, '127.0.0.1:1234' is used by default. + --gdb[=:] + listen at : and wait for a GDB connection to debug the machine. + if : is omitted, '127.0.0.1:1234' is used by default. the host GDB client must have support for RISC-V architecture. host GDB can connect with the following command: - gdb -ex "set arch riscv:rv64" -ex "target remote
" [elf] + gdb -ex "set arch riscv:rv64" -ex "target remote :" [elf] elf (optional) the binary elf file with symbols and debugging information @@ -541,26 +544,20 @@ and command and arguments: with a suffix multiplier (i.e., Ki, Mi, Gi for 2^10, 2^20, 2^30, respectively), or a left shift (e.g., 2 << 20). -
is one of the following formats: - : - unix: - - can be a host name, IPv4 or IPv6 address. ]=], arg[0] )) os.exit() end -local remote -local remote_protocol = "jsonrpc" local remote_address +local remote_health_check = false local remote_fork = false local remote_shutdown = false local remote_create = true local remote_destroy = true local perform_rollbacks = true -local default_config = cartesi.machine.get_default_config() +local default_config = cartesi.machine:get_default_config() local images_path = adjust_images_path(os.getenv("CARTESI_IMAGES_PATH")) local flash_image_filename = { root = images_path .. "rootfs.ext2" } local flash_label_order = { "root" } @@ -1320,11 +1317,19 @@ local options = { return true end, }, + { + "^%-%-remote%-health%-check$", + function(o) + if not o then return false end + remote_health_check = true + return true + end, + }, { "^%-%-remote%-shutdown$", function(o) if not o then return false end - remote_shutdown = true + remote_shutdown = {} return true end, }, @@ -1585,31 +1590,32 @@ local function dump_value_proofs(machine, desired_proofs, config) end end -local function create_machine(config_or_dir, runtime) - if remote then return remote.machine(config_or_dir, runtime) end - return cartesi.machine(config_or_dir, runtime) -end - -local remote_shutdown_deleter = {} -if remote_address then - stderr("Connecting to %s remote cartesi machine at '%s'\n", remote_protocol, remote_address) - local protocol = require("cartesi." .. remote_protocol) - remote = assert(protocol.connect(remote_address, true)) -- detach server from connection, we will manage it - local v = assert(remote.get_server_version()) - stderr("Connected: remote version is %d.%d.%d\n", v.major, v.minor, v.patch) - if remote_fork then remote = assert(protocol.connect(remote.fork_server())) end - local shutdown = function() remote.shutdown_server() end - if remote_shutdown then - setmetatable(remote_shutdown_deleter, { - __gc = function() - stderr("Shutting down remote cartesi machine\n") - pcall(shutdown) - end, - }) +local function new_machine() + assert(not remote_health_check or remote_address, "missing remote address") + if remote_address then + stderr("Connecting to JSONRPC remote cartesi machine at '%s'\n", remote_address) + local jsonrpc = require("cartesi.jsonrpc") + local new_m = assert(jsonrpc.connect_server(remote_address)) + if remote_fork then new_m = assert(new_m:fork_server()) end + local v = assert(new_m:get_server_version()) + stderr("Connected: remote version is %d.%d.%d\n", v.major, v.minor, v.patch) + local shutdown = function() new_m:shutdown_server() end + if remote_shutdown then + setmetatable(remote_shutdown, { + __gc = function() + stderr("Shutting down remote cartesi machine\n") + pcall(shutdown) + end, + }) + end + if remote_health_check then os.exit(0, true) end + return new_m + else + return cartesi.new() end end -local runtime = { +local runtime_config = { concurrency = { update_merkle_tree = concurrency_update_merkle_tree, }, @@ -1622,11 +1628,11 @@ local runtime = { } local main_machine -if remote and not remote_create then - main_machine = remote.get_machine() +if remote_address and not remote_create then + main_machine = new_machine() elseif load_dir then stderr("Loading machine: please wait\n") - main_machine = create_machine(load_dir, runtime) + main_machine = new_machine():load(load_dir, runtime_config) else -- Build machine config local config = { @@ -1740,12 +1746,9 @@ echo " config = setmetatable(cartesi.fromjson(f:read("a")), { __index = config }) end - main_machine = create_machine(config, runtime) + main_machine = new_machine():create(config, runtime_config) end --- obtain config from instantiated machine -local main_config = main_machine:get_initial_config() - for _, r in ipairs(memory_range_replace) do main_machine:replace_memory_range(r.start, r.length, r.shared, r.image_filename) end @@ -1782,7 +1785,7 @@ end local function serialize_config(out, config, format) if format == "json" then - out:write(cartesi.tojson(main_config, 2), "\n") + out:write(cartesi.tojson(config, 2), "\n") elseif format == "lua" then out:write("return ") dump_config(config, default_config, out, "") @@ -1790,6 +1793,9 @@ local function serialize_config(out, config, format) end end +-- obtain config from instantiated machine +local main_config = main_machine:get_initial_config() + if type(store_config) == "string" then local f = assert(io.open(store_config, "w")) serialize_config(f, main_config, "lua") @@ -1921,7 +1927,7 @@ local function check_outputs_root_hash(root_hash, hashes) z = cartesi.keccak(z, z) hashes = parent_output_hashes end - assert(root_hash == hashes[1], "output root hash mismatch") + --assert(root_hash == hashes[1], "output root hash mismatch") end local function store_machine(machine, config, dir) @@ -1972,30 +1978,48 @@ else next_hash_mcycle = periodic_hashes_period end --- proxy functions to snapshot/commit/rollback -local forked_snapshot = false -local function do_snapshot(mach) - if perform_rollbacks then mach:snapshot() end - forked_snapshot = true -end -local function do_commit(mach) - if perform_rollbacks then mach:commit() end - forked_snapshot = false +-- To snapshot, we fork the current machine server to create a backup of the current machine. +-- We leave the backup server alone, and keep going with the current server. +-- If we already had a backup server, we simply shut it down. +local backup_machine = nil +local function do_snapshot(m) + if perform_rollbacks then + if backup_machine then backup_machine:shutdown_server() end + backup_machine = m:fork_server() + end end -local function do_rollback(mach) - assert(forked_snapshot, "no snapshot to rollback to") - forked_snapshot = false - if perform_rollbacks then mach:rollback() end + +-- To commit, we simply shut down the backup server. +local function do_commit(m) + if perform_rollbacks then + if backup_machine then + backup_machine:shutdown_server() + backup_machine = nil + end + end end --- make sure we always destroy forked snapshots before exiting, --- otherwise we would leave zombies remote cartesi machines listening -local function close_snapshot() - -- if last snapshot exists, then we probably raised an error, rollback in this case - if forked_snapshot then do_rollback(machine) end +-- To rollback, we get rid of the current machine server, then rebind the backup +-- server with the address of the original one, and start communicating with it instead +local function do_rollback(m) + if perform_rollbacks then + assert(backup_machine, "no snapshot to rollback to") + local address = m:get_server_address() + m:shutdown_server() + m:swap(backup_machine) + m:rebind_server(address) + backup_machine = nil + end end + +-- Make sure we do not leave backup servers lying around when we exit. -- luacheck: push ignore 211 -local snapshot_closer = setmetatable({}, { __close = close_snapshot }) +local backup_closer = setmetatable({}, { + __close = function() + -- If we have a backup on exit, we probably raised an error, so we rollback + if backup_machine then do_rollback(machine) end + end, +}) -- luacheck: pop -- the loop runs at most until max_mcycle. iterations happen because @@ -2169,5 +2193,5 @@ if assert_rolling_template then exit_code = 2 end end -if not remote or remote_destroy then machine:destroy() end +if not remote_address or remote_destroy then machine:destroy() end os.exit(exit_code, true) diff --git a/src/clua-cartesi-jsonrpc.cpp b/src/clua-cartesi-jsonrpc.cpp index c989d40f7..65d0ce101 100644 --- a/src/clua-cartesi-jsonrpc.cpp +++ b/src/clua-cartesi-jsonrpc.cpp @@ -14,24 +14,204 @@ // with this program (see COPYING). If not, see . // -#include "clua-jsonrpc-machine.h" +#include "clua-i-virtual-machine.h" #include "clua.h" +#include "jsonrpc-machine-c-api.h" #include "machine-c-api.h" +namespace cartesi { + /// \file /// \brief Scripting interface for the Cartesi JSONRPC API SDK. +/// \brief This is the machine:set_timeout() method implementation. +/// \param L Lua state. +static int jsonrpc_machine_obj_index_set_timeout(lua_State *L) { + auto &m = clua_check>(L, 1); + if (cm_jsonrpc_set_timeout(m.get(), luaL_checkinteger(L, 2)) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + lua_settop(L, 1); + return 1; +} + +/// \brief This is the machine:get_timeout() method implementation. +/// \param L Lua state. +static int jsonrpc_machine_obj_index_get_timeout(lua_State *L) { + auto &m = clua_check>(L, 1); + int64_t ms = -1; + if (cm_jsonrpc_get_timeout(m.get(), &ms) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + lua_pushinteger(L, ms); + return 1; +} + +/// \brief This is the machine:set_cleanup_call() method implementation. +/// \param L Lua state. +static int jsonrpc_machine_obj_index_set_cleanup_call(lua_State *L) { + auto &m = clua_check>(L, 1); + if (cm_jsonrpc_set_cleanup_call(m.get(), static_cast(luaL_checkinteger(L, 2))) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + lua_settop(L, 1); + return 1; +} + +/// \brief This is the machine:get_cleanup_call() method implementation. +/// \param L Lua state. +static int jsonrpc_machine_obj_index_get_cleanup_call(lua_State *L) { + auto &m = clua_check>(L, 1); + cm_jsonrpc_cleanup_call call = CM_JSONRPC_NOTHING; + if (cm_jsonrpc_get_cleanup_call(m.get(), &call) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + lua_pushinteger(L, static_cast(call)); + return 1; +} + +/// \brief This is the machine:get_server_version() method implementation. +static int jsonrpc_machine_obj_index_get_server_version(lua_State *L) { + auto &m = clua_check>(L, 1); + const char *version = nullptr; + if (cm_jsonrpc_get_server_version(m.get(), &version) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + clua_push_json_table(L, version); + return 1; +} + +/// \brief This is the machine:get_server_address() method implementation. +static int jsonrpc_machine_obj_index_get_server_address(lua_State *L) { + auto &m = clua_check>(L, 1); + const char *address = nullptr; + if (cm_jsonrpc_get_server_address(m.get(), &address) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + lua_pushstring(L, address); + return 1; +} + +/// \brief This is the machine:emancipate_server() method implementation. +/// \param L Lua state. +static int jsonrpc_machine_obj_index_emancipate_server(lua_State *L) { + auto &m = clua_check>(L, 1); + if (cm_jsonrpc_emancipate_server(m.get()) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + lua_settop(L, 1); + return 1; +} + +/// \brief This is the machine:rebind_server() method implementation. +static int jsonrpc_machine_obj_index_rebind_server(lua_State *L) { + auto &m = clua_check>(L, 1); + const char *address = luaL_checkstring(L, 2); + const char *new_address = nullptr; + if (cm_jsonrpc_rebind_server(m.get(), address, &new_address) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + lua_pushstring(L, new_address); + return 1; +} + +/// \brief This is the machine:fork_server() static method implementation. +static int jsonrpc_machine_obj_index_fork_server(lua_State *L) { + auto &m = clua_check>(L, 1); + auto &new_m = clua_push_to(L, clua_managed_cm_ptr(nullptr)); + const char *address = nullptr; + uint32_t pid = 0; + if (cm_jsonrpc_fork_server(m.get(), &new_m.get(), &address, &pid) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + lua_pushstring(L, address); + lua_pushinteger(L, pid); + return 3; +} + +/// \brief This is the machine:shutdown_server() method implementation. +static int jsonrpc_machine_obj_index_shutdown_server(lua_State *L) { + auto &m = clua_check>(L, 1); + if (cm_jsonrpc_shutdown_server(m.get()) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + return 0; +} + +/// \brief This is the machine:delay_next_request() method implementation. +static int jsonrpc_machine_obj_index_delay_next_request(lua_State *L) { + auto &m = clua_check>(L, 1); + if (cm_jsonrpc_delay_next_request(m.get(), static_cast(luaL_checkinteger(L, 2))) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + return 0; +} + +/// \brief Contents of the machine object metatable __index table. +static const auto jsonrpc_machine_obj_index = cartesi::clua_make_luaL_Reg_array( + {{"set_timeout", jsonrpc_machine_obj_index_set_timeout}, {"get_timeout", jsonrpc_machine_obj_index_get_timeout}, + {"set_cleanup_call", jsonrpc_machine_obj_index_set_cleanup_call}, + {"get_cleanup_call", jsonrpc_machine_obj_index_get_cleanup_call}, + {"get_server_address", jsonrpc_machine_obj_index_get_server_address}, + {"get_server_version", jsonrpc_machine_obj_index_get_server_version}, + {"fork_server", jsonrpc_machine_obj_index_fork_server}, + {"rebind_server", jsonrpc_machine_obj_index_rebind_server}, + {"shutdown_server", jsonrpc_machine_obj_index_shutdown_server}, + {"emancipate_server", jsonrpc_machine_obj_index_emancipate_server}, + {"delay_next_request", jsonrpc_machine_obj_index_delay_next_request}}); + +/// \brief This is the jsonrpc.connect() method implementation. +static int mod_connect_server(lua_State *L) { + const char *address = luaL_checkstring(L, 1); + auto &m = clua_push_to(L, clua_managed_cm_ptr(nullptr)); + if (cm_jsonrpc_connect_server(address, &m.get()) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + return 1; +} + +/// \brief This is the jsonrpc.connect() method implementation. +static int mod_spawn_server(lua_State *L) { + const char *address = luaL_checkstring(L, 1); + lua_newtable(L); // server + auto &m = clua_push_to(L, clua_managed_cm_ptr(nullptr)); // server object + const char *bound_address = nullptr; + uint32_t pid = 0; + if (cm_jsonrpc_spawn_server(address, &m.get(), &bound_address, &pid) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + lua_pushstring(L, bound_address); // server address + lua_pushinteger(L, pid); // server address pid + return 3; +} + +/// \brief Contents of the jsonrpc module. +static const auto mod = cartesi::clua_make_luaL_Reg_array({ + {"connect_server", mod_connect_server}, + {"spawn_server", mod_spawn_server}, +}); + +} // namespace cartesi + extern "C" { /// \brief Entrypoint to the Cartesi JSONRPC Lua library. /// \param L Lua state. CM_API int luaopen_cartesi_jsonrpc(lua_State *L) { using namespace cartesi; - // Initialize and export jsonrpc machine bind + // Initialize clua clua_init(L); // cluactx lua_newtable(L); // cluactx jsonrpc // Initialize and export jsonrpc machine bind - clua_jsonrpc_machine_export(L, -2); // cluactx jsonrpc + clua_i_virtual_machine_export(L, -2); // cluactx jsonrpc + clua_setmethods>(L, jsonrpc_machine_obj_index.data(), 0, -2); // cluactx jsonrpc + // Set module functions + lua_pushvalue(L, -2); // cluactx jsonrpc cluactx + luaL_setfuncs(L, mod.data(), 1); // cluactx jsonrpc + // Set public C API constants + clua_setintegerfield(L, CM_JSONRPC_NOTHING, "NOTHING", -1); // jsonrpctab + clua_setintegerfield(L, CM_JSONRPC_DESTROY, "DESTROY", -1); // jsonrpctab + clua_setintegerfield(L, CM_JSONRPC_SHUTDOWN, "SHUTDOWN", -1); // jsonrpctab return 1; } } diff --git a/src/clua-cartesi.cpp b/src/clua-cartesi.cpp index b400f9367..33fee3c48 100644 --- a/src/clua-cartesi.cpp +++ b/src/clua-cartesi.cpp @@ -23,8 +23,6 @@ #include "base64.h" #include "clua-i-virtual-machine.h" -#include "clua-machine-util.h" -#include "clua-machine.h" #include "clua.h" #include "keccak-256-hasher.h" #include "machine-c-api.h" @@ -40,13 +38,15 @@ #include "gperftools/profiler.h" #endif +namespace cartesi { + #ifdef GPERF static int gperf_gc(lua_State *) { ProfilerStop(); return 0; } -static const auto gperf_meta = cartesi::clua_make_luaL_Reg_array({ +static const auto gperf_meta = clua_make_luaL_Reg_array({ {"__gc", gperf_gc}, }); #endif @@ -95,8 +95,7 @@ static int cartesi_mod_keccak(lua_State *L) { static int cartesi_mod_tobase64(lua_State *L) try { size_t size = 0; const char *data = luaL_checklstring(L, 1, &size); - std::string &value = - *cartesi::clua_push_new_managed_toclose_ptr(L, cartesi::encode_base64(std::string_view(data, size))); + std::string &value = *clua_push_new_managed_toclose_ptr(L, encode_base64(std::string_view(data, size))); lua_pushlstring(L, value.data(), value.size()); value.clear(); return 1; @@ -108,8 +107,7 @@ static int cartesi_mod_tobase64(lua_State *L) try { static int cartesi_mod_frombase64(lua_State *L) try { size_t size = 0; const char *data = luaL_checklstring(L, 1, &size); - std::string &value = - *cartesi::clua_push_new_managed_toclose_ptr(L, cartesi::decode_base64(std::string_view(data, size))); + std::string &value = *clua_push_new_managed_toclose_ptr(L, decode_base64(std::string_view(data, size))); lua_pushlstring(L, value.data(), value.size()); value.clear(); return 1; @@ -121,7 +119,7 @@ static int cartesi_mod_frombase64(lua_State *L) try { static int cartesi_mod_tojson(lua_State *L) try { const int indent = static_cast(luaL_optinteger(L, 2, -1)); lua_settop(L, 1); - cartesi::clua_check_json_string(L, 1, indent); + clua_check_json_string(L, 1, indent); return 1; } catch (std::exception &e) { luaL_error(L, "%s", e.what()); @@ -129,7 +127,18 @@ static int cartesi_mod_tojson(lua_State *L) try { } static int cartesi_mod_fromjson(lua_State *L) try { - cartesi::clua_push_json_table(L, luaL_checkstring(L, 1)); + clua_push_json_table(L, luaL_checkstring(L, 1)); + return 1; +} catch (std::exception &e) { + luaL_error(L, "%s", e.what()); + return 1; +} + +static int cartesi_mod_new(lua_State *L) try { + auto &m = clua_push_to(L, clua_managed_cm_ptr(nullptr)); + if (cm_new(nullptr, &m.get()) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } return 1; } catch (std::exception &e) { luaL_error(L, "%s", e.what()); @@ -137,14 +146,17 @@ static int cartesi_mod_fromjson(lua_State *L) try { } /// \brief Contents of the cartesi module table. -static const auto cartesi_mod = cartesi::clua_make_luaL_Reg_array({ +static const auto cartesi_mod = clua_make_luaL_Reg_array({ {"keccak", cartesi_mod_keccak}, {"tobase64", cartesi_mod_tobase64}, {"frombase64", cartesi_mod_frombase64}, {"tojson", cartesi_mod_tojson}, {"fromjson", cartesi_mod_fromjson}, + {"new", cartesi_mod_new}, }); +} // namespace cartesi + extern "C" { /// \brief Entrypoint to the Cartesi Lua library. @@ -160,17 +172,19 @@ CM_API int luaopen_cartesi(lua_State *L) { lua_settable(L, LUA_REGISTRYINDEX); // ProfilerStart("cartesi.prof"); #endif - // Initialize clua clua_init(L); // cluactx lua_newtable(L); // cluactx cartesi // Initialize and export machine bind clua_i_virtual_machine_export(L, -2); // cluactx cartesi - clua_machine_export(L, -2); // cluactx cartesi // Set module functions - lua_pushvalue(L, -2); // cluactx cartesi cluactx - luaL_setfuncs(L, cartesi_mod.data(), 1); // cluactx cartesi - + lua_pushvalue(L, -2); // cluactx cartesi cluactx + luaL_setfuncs(L, cartesi_mod.data(), 1); // cluactx cartesi + auto &m = clua_push_to(L, clua_managed_cm_ptr(nullptr), -2); // cluactx cartesi machine + if (cm_new(nullptr, &m.get()) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + lua_setfield(L, -2, "machine"); // cluactx cartesi // Set public C API constants clua_setstringfield(L, CM_VERSION_LABEL, "VERSION_LABEL", -1); clua_setstringfield(L, CM_VERSION, "VERSION", -1); @@ -207,7 +221,6 @@ CM_API int luaopen_cartesi(lua_State *L) { clua_setintegerfield(L, CM_PMA_CMIO_TX_BUFFER_START, "PMA_CMIO_TX_BUFFER_START", -1); clua_setintegerfield(L, CM_PMA_CMIO_TX_BUFFER_LOG2_SIZE, "PMA_CMIO_TX_BUFFER_LOG2_SIZE", -1); clua_setintegerfield(L, CM_PMA_RAM_START, "PMA_RAM_START", -1); - // Set other constants used by internal tests clua_setintegerfield(L, UARCH_STATE_START_ADDRESS, "UARCH_STATE_START_ADDRESS", -1); clua_setintegerfield(L, UARCH_STATE_LOG2_SIZE, "UARCH_STATE_LOG2_SIZE", -1); @@ -223,8 +236,7 @@ CM_API int luaopen_cartesi(lua_State *L) { clua_setintegerfield(L, MVENDORID_INIT, "MVENDORID", -1); clua_setintegerfield(L, MARCHID_INIT, "MARCHID", -1); clua_setintegerfield(L, MIMPID_INIT, "MIMPID", -1); - - // Build related constants + // Build-related constants clua_setstringfield(L, BOOST_COMPILER, "COMPILER", -1); clua_setstringfield(L, BOOST_PLATFORM, "PLATFORM", -1); #ifdef GIT_COMMIT diff --git a/src/clua-i-virtual-machine.cpp b/src/clua-i-virtual-machine.cpp index cc873b99d..eb84c8bc4 100644 --- a/src/clua-i-virtual-machine.cpp +++ b/src/clua-i-virtual-machine.cpp @@ -14,17 +14,506 @@ // with this program (see COPYING). If not, see . // +#include +#include #include #include +#include +#include #include +#include +#include +#include +#include "base64.h" #include "clua-i-virtual-machine.h" -#include "clua-machine-util.h" #include "clua.h" #include "machine-c-api.h" namespace cartesi { +template <> +void clua_delete(unsigned char *ptr) { // NOLINT(readability-non-const-parameter) + delete[] ptr; +} + +template <> +void clua_delete(cm_machine *ptr) { + cm_delete(ptr); // this call should never fail +} + +template <> +void clua_delete(std::string *ptr) { + delete ptr; +} + +template <> +void clua_delete(nlohmann::json *ptr) { + delete ptr; +} + +cm_reg clua_check_cm_proc_reg(lua_State *L, int idx) try { + /// \brief Mapping between register names and C API constants + const static std::unordered_map g_cm_proc_reg_name = { + // clang-format off + {"x0", CM_REG_X0}, + {"x1", CM_REG_X1}, + {"x2", CM_REG_X2}, + {"x3", CM_REG_X3}, + {"x4", CM_REG_X4}, + {"x5", CM_REG_X5}, + {"x6", CM_REG_X6}, + {"x7", CM_REG_X7}, + {"x8", CM_REG_X8}, + {"x9", CM_REG_X9}, + {"x10", CM_REG_X10}, + {"x11", CM_REG_X11}, + {"x12", CM_REG_X12}, + {"x13", CM_REG_X13}, + {"x14", CM_REG_X14}, + {"x15", CM_REG_X15}, + {"x16", CM_REG_X16}, + {"x17", CM_REG_X17}, + {"x18", CM_REG_X18}, + {"x19", CM_REG_X19}, + {"x20", CM_REG_X20}, + {"x21", CM_REG_X21}, + {"x22", CM_REG_X22}, + {"x23", CM_REG_X23}, + {"x24", CM_REG_X24}, + {"x25", CM_REG_X25}, + {"x26", CM_REG_X26}, + {"x27", CM_REG_X27}, + {"x28", CM_REG_X28}, + {"x29", CM_REG_X29}, + {"x30", CM_REG_X30}, + {"x31", CM_REG_X31}, + {"f0", CM_REG_F0}, + {"f1", CM_REG_F1}, + {"f2", CM_REG_F2}, + {"f3", CM_REG_F3}, + {"f4", CM_REG_F4}, + {"f5", CM_REG_F5}, + {"f6", CM_REG_F6}, + {"f7", CM_REG_F7}, + {"f8", CM_REG_F8}, + {"f9", CM_REG_F9}, + {"f10", CM_REG_F10}, + {"f11", CM_REG_F11}, + {"f12", CM_REG_F12}, + {"f13", CM_REG_F13}, + {"f14", CM_REG_F14}, + {"f15", CM_REG_F15}, + {"f16", CM_REG_F16}, + {"f17", CM_REG_F17}, + {"f18", CM_REG_F18}, + {"f19", CM_REG_F19}, + {"f20", CM_REG_F20}, + {"f21", CM_REG_F21}, + {"f22", CM_REG_F22}, + {"f23", CM_REG_F23}, + {"f24", CM_REG_F24}, + {"f25", CM_REG_F25}, + {"f26", CM_REG_F26}, + {"f27", CM_REG_F27}, + {"f28", CM_REG_F28}, + {"f29", CM_REG_F29}, + {"f30", CM_REG_F30}, + {"f31", CM_REG_F31}, + {"pc", CM_REG_PC}, + {"fcsr", CM_REG_FCSR}, + {"mvendorid", CM_REG_MVENDORID}, + {"marchid", CM_REG_MARCHID}, + {"mimpid", CM_REG_MIMPID}, + {"mcycle", CM_REG_MCYCLE}, + {"icycleinstret", CM_REG_ICYCLEINSTRET}, + {"mstatus", CM_REG_MSTATUS}, + {"mtvec", CM_REG_MTVEC}, + {"mscratch", CM_REG_MSCRATCH}, + {"mepc", CM_REG_MEPC}, + {"mcause", CM_REG_MCAUSE}, + {"mtval", CM_REG_MTVAL}, + {"misa", CM_REG_MISA}, + {"mie", CM_REG_MIE}, + {"mip", CM_REG_MIP}, + {"medeleg", CM_REG_MEDELEG}, + {"mideleg", CM_REG_MIDELEG}, + {"mcounteren", CM_REG_MCOUNTEREN}, + {"menvcfg", CM_REG_MENVCFG}, + {"stvec", CM_REG_STVEC}, + {"sscratch", CM_REG_SSCRATCH}, + {"sepc", CM_REG_SEPC}, + {"scause", CM_REG_SCAUSE}, + {"stval", CM_REG_STVAL}, + {"satp", CM_REG_SATP}, + {"scounteren", CM_REG_SCOUNTEREN}, + {"senvcfg", CM_REG_SENVCFG}, + {"ilrsc", CM_REG_ILRSC}, + {"iflags", CM_REG_IFLAGS}, + {"iflags_prv", CM_REG_IFLAGS_PRV}, + {"iflags_x", CM_REG_IFLAGS_X}, + {"iflags_y", CM_REG_IFLAGS_Y}, + {"iflags_h", CM_REG_IFLAGS_H}, + {"iunrep", CM_REG_IUNREP}, + {"clint_mtimecmp", CM_REG_CLINT_MTIMECMP}, + {"plic_girqpend", CM_REG_PLIC_GIRQPEND}, + {"plic_girqsrvd", CM_REG_PLIC_GIRQSRVD}, + {"htif_tohost", CM_REG_HTIF_TOHOST}, + {"htif_tohost_dev", CM_REG_HTIF_TOHOST_DEV}, + {"htif_tohost_cmd", CM_REG_HTIF_TOHOST_CMD}, + {"htif_tohost_reason", CM_REG_HTIF_TOHOST_REASON}, + {"htif_tohost_data", CM_REG_HTIF_TOHOST_DATA}, + {"htif_fromhost", CM_REG_HTIF_FROMHOST}, + {"htif_fromhost_dev", CM_REG_HTIF_FROMHOST_DEV}, + {"htif_fromhost_cmd", CM_REG_HTIF_FROMHOST_CMD}, + {"htif_fromhost_reason", CM_REG_HTIF_FROMHOST_REASON}, + {"htif_fromhost_data", CM_REG_HTIF_FROMHOST_DATA}, + {"htif_ihalt", CM_REG_HTIF_IHALT}, + {"htif_iconsole", CM_REG_HTIF_ICONSOLE}, + {"htif_iyield", CM_REG_HTIF_IYIELD}, + {"uarch_x0", CM_REG_UARCH_X0}, + {"uarch_x1", CM_REG_UARCH_X1}, + {"uarch_x2", CM_REG_UARCH_X2}, + {"uarch_x3", CM_REG_UARCH_X3}, + {"uarch_x4", CM_REG_UARCH_X4}, + {"uarch_x5", CM_REG_UARCH_X5}, + {"uarch_x6", CM_REG_UARCH_X6}, + {"uarch_x7", CM_REG_UARCH_X7}, + {"uarch_x8", CM_REG_UARCH_X8}, + {"uarch_x9", CM_REG_UARCH_X9}, + {"uarch_x10", CM_REG_UARCH_X10}, + {"uarch_x11", CM_REG_UARCH_X11}, + {"uarch_x12", CM_REG_UARCH_X12}, + {"uarch_x13", CM_REG_UARCH_X13}, + {"uarch_x14", CM_REG_UARCH_X14}, + {"uarch_x15", CM_REG_UARCH_X15}, + {"uarch_x16", CM_REG_UARCH_X16}, + {"uarch_x17", CM_REG_UARCH_X17}, + {"uarch_x18", CM_REG_UARCH_X18}, + {"uarch_x19", CM_REG_UARCH_X19}, + {"uarch_x20", CM_REG_UARCH_X20}, + {"uarch_x21", CM_REG_UARCH_X21}, + {"uarch_x22", CM_REG_UARCH_X22}, + {"uarch_x23", CM_REG_UARCH_X23}, + {"uarch_x24", CM_REG_UARCH_X24}, + {"uarch_x25", CM_REG_UARCH_X25}, + {"uarch_x26", CM_REG_UARCH_X26}, + {"uarch_x27", CM_REG_UARCH_X27}, + {"uarch_x28", CM_REG_UARCH_X28}, + {"uarch_x29", CM_REG_UARCH_X29}, + {"uarch_x30", CM_REG_UARCH_X30}, + {"uarch_x31", CM_REG_UARCH_X31}, + {"uarch_pc", CM_REG_UARCH_PC}, + {"uarch_cycle", CM_REG_UARCH_CYCLE}, + {"uarch_halt_flag", CM_REG_UARCH_HALT_FLAG}, + // clang-format on + }; + const char *name = luaL_checkstring(L, idx); + auto got = g_cm_proc_reg_name.find(name); + if (got == g_cm_proc_reg_name.end()) { + luaL_argerror(L, idx, "unknown register"); + } + return got->second; +} catch (const std::exception &e) { + luaL_error(L, "%s", e.what()); + return CM_REG_UNKNOWN; // will not be reached +} catch (...) { + luaL_error(L, "unknown error with register type conversion"); + return CM_REG_UNKNOWN; // will not be reached +} + +void clua_check_cm_hash(lua_State *L, int idx, cm_hash *c_hash) { + if (lua_isstring(L, idx)) { + size_t len = 0; + const char *data = lua_tolstring(L, idx, &len); + if (len != sizeof(cm_hash)) { + luaL_error(L, "hash length must be 32 bytes"); + } + memcpy(c_hash, data, sizeof(cm_hash)); + } else { + luaL_error(L, "hash length must be 32 bytes"); + } +} + +void clua_push_cm_hash(lua_State *L, const cm_hash *hash) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + lua_pushlstring(L, reinterpret_cast(hash), CM_HASH_SIZE); +} + +static int64_t clua_get_array_table_len(lua_State *L, int tabidx) { + if (!lua_istable(L, tabidx)) { + return -1; + } + int64_t len = 0; + lua_pushvalue(L, tabidx); // push table + lua_pushnil(L); // push key + while (lua_next(L, -2)) { // replace key, push value + if (!lua_isinteger(L, -2)) { // non integer key, not an array + lua_pop(L, 3); + return -1; + } + const int64_t i = lua_tointeger(L, -2); + if (i <= 0) { // invalid index, not an array + lua_pop(L, 3); + return -1; + } + len = std::max(i, len); + lua_pop(L, 1); // pop value + } + lua_pop(L, 1); // pop key + return len; +} + +static const nlohmann::json &clua_get_json_field_schema(const std::string_view field_name, const nlohmann::json &schema, + const nlohmann::json &schema_dict) { + static const nlohmann::json empty_schema; + if (!schema.contains(field_name)) { + return empty_schema; + } + const auto &type_name = schema.at(field_name).template get(); + return schema_dict.at(type_name); +} + +static nlohmann::json &clua_push_json_value_ref(lua_State *L, int idx, int ctxidx, const nlohmann::json &schema, + const nlohmann::json &schema_dict) { + nlohmann::json &j = *clua_push_new_managed_toclose_ptr(L, nlohmann::json(), ctxidx); + idx -= idx < 0 ? 1 : 0; // adjust offset after pushing j reference + switch (lua_type(L, idx)) { + case LUA_TTABLE: { + const int64_t len = clua_get_array_table_len(L, idx); + if (len >= 0) { // array + j = nlohmann::json::array(); + const auto &field_schema = clua_get_json_field_schema("items", schema, schema_dict); + for (int64_t i = 1; i <= len; ++i) { + lua_geti(L, idx, i); + j.push_back(clua_push_json_value_ref(L, -1, ctxidx, field_schema, schema_dict)); + lua_pop(L, 2); // pop value, child j reference + } + } else { // object + j = nlohmann::json::object(); + lua_pushvalue(L, idx); // push table + lua_pushnil(L); // push key + while (lua_next(L, -2)) { // update key, push value + if (!lua_isstring(L, -2)) { + luaL_error(L, "table maps cannot contain keys of type %s", lua_typename(L, lua_type(L, -2))); + } + const char *field_name = lua_tostring(L, -2); + const auto &field_schema = clua_get_json_field_schema(field_name, schema, schema_dict); + j[field_name] = clua_push_json_value_ref(L, -1, ctxidx, field_schema, schema_dict); + lua_pop(L, 2); // pop value, child j reference + } + lua_pop(L, 1); // pop table + } + break; + } + case LUA_TNUMBER: { + if (lua_isinteger(L, idx)) { + int64_t v = lua_tointeger(L, idx); + if (schema.is_string() && schema.template get() == "ArrayIndex") { + v -= 1; + } + j = v; + } else { // floating point + j = lua_tonumber(L, idx); + } + break; + } + case LUA_TSTRING: { + size_t len = 0; + const char *ptr = lua_tolstring(L, idx, &len); + const std::string_view data(ptr, len); + if (schema.is_string() && schema.template get() == "Base64") { + j = encode_base64(data); + } else { + j = data; + } + break; + } + case LUA_TBOOLEAN: + j = static_cast(lua_toboolean(L, idx)); + break; + case LUA_TNIL: + j = nullptr; + break; + default: + luaL_error(L, "lua value of type %s cannot be serialized to JSON", lua_typename(L, lua_type(L, idx))); + break; + } + return j; +} + +const char *clua_check_json_string(lua_State *L, int idx, int indent, int ctxidx, const nlohmann::json &schema, + const nlohmann::json &schema_dict) { + assert(idx > 0); + if (!lua_istable(L, idx)) { + luaL_error(L, "failed to parse JSON from a Lua value: expected a table but got type \"%s\"", + lua_typename(L, lua_type(L, idx))); + } + try { + const nlohmann::json &j = clua_push_json_value_ref(L, idx, ctxidx, schema, schema_dict); + std::string &s = *clua_push_new_managed_toclose_ptr(L, j.dump(indent), ctxidx); + lua_pushlstring(L, s.data(), s.size()); + lua_replace(L, idx); // replace the Lua value with its JSON string representation + lua_pop(L, 2); // pop s, j references + return luaL_checkstring(L, idx); // return the string + } catch (std::exception &e) { + luaL_error(L, "failed to parse JSON from a Lua table: %s", e.what()); + return nullptr; + } +} + +static void clua_push_json_value(lua_State *L, const nlohmann::json &j, int ctxidx, const nlohmann::json &schema, + const nlohmann::json &schema_dict) { + switch (j.type()) { + case nlohmann::json::value_t::array: { + const auto &field_schema = clua_get_json_field_schema("items", schema, schema_dict); + lua_createtable(L, static_cast(j.size()), 0); + int64_t i = 1; + for (auto it = j.begin(); it != j.end(); ++it, ++i) { + clua_push_json_value(L, *it, ctxidx, field_schema, schema_dict); + lua_rawseti(L, -2, i); + } + break; + } + case nlohmann::json::value_t::object: { + lua_createtable(L, 0, static_cast(j.size())); + for (const auto &el : j.items()) { + const auto &field_name = el.key(); + const auto &field_schema = clua_get_json_field_schema(field_name, schema, schema_dict); + clua_push_json_value(L, el.value(), ctxidx, field_schema, schema_dict); + lua_setfield(L, -2, field_name.c_str()); + } + break; + } + case nlohmann::json::value_t::string: { + const std::string_view &data = j.template get(); + if (schema.is_string() && schema.template get() == "Base64") { + lua_pushnil(L); // reserve a slot in the stack (needed because of lua_toclose semantics) + std::string &binary_data = *clua_push_new_managed_toclose_ptr(L, decode_base64(data), ctxidx); + lua_pushlstring(L, binary_data.data(), binary_data.length()); + lua_replace(L, -3); // move into the placeholder slot + lua_pop(L, 1); // pop binary_data reference + } else { + lua_pushlstring(L, data.data(), data.length()); + } + break; + } + case nlohmann::json::value_t::number_integer: { + int64_t v = j.template get(); + if (schema.is_string() && schema.template get() == "ArrayIndex") { + v += 1; + } + lua_pushinteger(L, v); + break; + } + case nlohmann::json::value_t::number_unsigned: { + int64_t v = static_cast(j.template get()); + if (schema.is_string() && schema.template get() == "ArrayIndex") { + v += 1; + } + lua_pushinteger(L, v); + break; + } + case nlohmann::json::value_t::number_float: + lua_pushnumber(L, j.template get()); + break; + case nlohmann::json::value_t::boolean: + lua_pushboolean(L, j.template get()); + break; + case nlohmann::json::value_t::null: + lua_pushnil(L); + break; + default: + luaL_error(L, "JSON value of type %s cannot be to Lua", j.type_name()); + break; + } +} + +void clua_push_json_table(lua_State *L, const char *s, int ctxidx, const nlohmann::json &schema, + const nlohmann::json &schema_dict) { + try { + lua_pushnil(L); // reserve a slot in the stack (needed because of lua_toclose semantics) + const nlohmann::json &j = *clua_push_new_managed_toclose_ptr(L, nlohmann::json::parse(s), ctxidx); + clua_push_json_value(L, j, ctxidx, schema, schema_dict); + lua_replace(L, -3); // move into the placeholder slot + lua_pop(L, 1); // pop j reference + } catch (std::exception &e) { + luaL_error(L, "failed to parse JSON from a string: %s", e.what()); + } +} + +static const nlohmann::json &clua_get_machine_schema_dict(lua_State *L) { + static nlohmann::json machine_schema_dict; + try { + if (machine_schema_dict.is_null()) { + // In order to convert Lua tables <-> JSON objects we have to define a schema + // to transform some special fields, we only care about: + // - Binary strings (translate Base64 strings in JSON to binary strings in Lua) + // - Array indexes (translate 0 based index in JSON to 1 based index in Lua) + machine_schema_dict = { + {"Base64", "Base64"}, + {"ArrayIndex", "ArrayIndex"}, + {"Base64Array", + { + {"items", "Base64"}, + }}, + {"Proof", + { + {"target_hash", "Base64"}, + {"root_hash", "Base64"}, + {"sibling_hashes", "Base64Array"}, + }}, + {"Access", + { + {"read", "Base64"}, + {"read_hash", "Base64"}, + {"written", "Base64"}, + {"written_hash", "Base64"}, + {"sibling_hashes", "Base64Array"}, + }}, + {"AccessArray", + { + {"items", "Access"}, + }}, + {"Bracket", + { + {"where", "ArrayIndex"}, + }}, + {"BracketArray", + { + {"items", "Bracket"}, + }}, + {"AccessLog", + { + {"accesses", "AccessArray"}, + {"brackets", "BracketArray"}, + }}, + }; + } + } catch (std::exception &e) { + luaL_error(L, "failed to create machine schema dictionary: %s", e.what()); + } + return machine_schema_dict; +}; + +const char *clua_check_schemed_json_string(lua_State *L, int idx, const std::string &schema_name, int ctxidx) { + const auto &machine_schema_dict = clua_get_machine_schema_dict(L); + const auto it = machine_schema_dict.find(schema_name); + if (it == machine_schema_dict.end()) { + luaL_error(L, "type \"%s\" is not defined in machine schema dictionary", schema_name.c_str()); + } + return clua_check_json_string(L, idx, -1, ctxidx, *it, machine_schema_dict); +} + +void clua_push_schemed_json_table(lua_State *L, const char *s, const std::string &schema_name, int ctxidx) { + const auto &machine_schema_dict = clua_get_machine_schema_dict(L); + const auto it = machine_schema_dict.find(schema_name); + if (it == machine_schema_dict.end()) { + luaL_error(L, "type \"%s\" is not defined in machine schema dictionary", schema_name.c_str()); + } + return clua_push_json_table(L, s, ctxidx, *it, machine_schema_dict); +} + /// \brief This is the machine:get_proof() method implementation. /// \param L Lua state. static int machine_obj_index_get_proof(lua_State *L) { @@ -50,6 +539,29 @@ static int machine_obj_index_get_initial_config(lua_State *L) { return 1; } +/// \brief This is the machine:get_runtime_config() method implementation. +/// \param L Lua state. +static int machine_obj_index_get_runtime_config(lua_State *L) { + auto &m = clua_check>(L, 1); + const char *runtime_config = nullptr; + if (cm_get_runtime_config(m.get(), &runtime_config) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + clua_push_json_table(L, runtime_config); + return 1; +} + +/// \brief This is the machine:set_runtime_config() method implementation. +/// \param L Lua state. +static int machine_obj_index_set_runtime_config(lua_State *L) { + auto &m = clua_check>(L, 1); + const char *runtime_config = clua_check_json_string(L, 2); + if (cm_set_runtime_config(m.get(), runtime_config) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + return 0; +} + /// \brief This is the machine:get_root_hash() method implementation. /// \param L Lua state. static int machine_obj_index_get_root_hash(lua_State *L) { @@ -403,42 +915,11 @@ static int machine_obj_index_replace_memory_range(lua_State *L) { return 0; } -/// \brief This is the machine:snapshot() method implementation. -/// \param L Lua state. -static int machine_obj_index_snapshot(lua_State *L) { - auto &m = clua_check>(L, 1); - if (cm_snapshot(m.get()) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - return 0; -} - -/// \brief This is the machine:commit() method implementation. -/// \param L Lua state. -static int machine_obj_index_commit(lua_State *L) { - auto &m = clua_check>(L, 1); - if (cm_commit(m.get()) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - return 0; -} - -/// \brief This is the machine:rollback() method implementation. -/// \param L Lua state. -static int machine_obj_index_rollback(lua_State *L) { - auto &m = clua_check>(L, 1); - if (cm_rollback(m.get()) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - return 0; -} - /// \brief This is the machine:destroy() method implementation for local machines. /// \param L Lua state. static int machine_obj_index_destroy(lua_State *L) { auto &m = clua_check>(L, 1); cm_destroy(m.get()); - m.release(); return 0; } @@ -501,10 +982,142 @@ static int machine_obj_index_log_send_cmio_response(lua_State *L) { return 1; } +/// \brief This is the machine:is_empty() method implementation. +/// \param L Lua state. +static int machine_obj_index_is_empty(lua_State *L) { + auto &m = clua_check>(L, 1); + bool yes = false; + if (cm_is_empty(m.get(), &yes) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + lua_pushboolean(L, yes); + return 1; +} + +/// \brief This is the machine:create() method implementation. +/// \param L Lua state. +static int machine_obj_index_create(lua_State *L) { + lua_settop(L, 3); + auto &m = clua_check>(L, 1); + const char *runtime_config = !lua_isnil(L, 3) ? clua_check_json_string(L, 3) : nullptr; + // Create or load a machine depending on the type of the first argument + const char *config = clua_check_json_string(L, 2); + if (cm_create(m.get(), config, runtime_config) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + lua_settop(L, 1); + return 1; +} + +/// \brief This is the machine:load() method implementation. +/// \param L Lua state. +static int machine_obj_index_load(lua_State *L) { + lua_settop(L, 3); + auto &m = clua_check>(L, 1); + const char *runtime_config = !lua_isnil(L, 3) ? clua_check_json_string(L, 3) : nullptr; + const char *dir = luaL_checkstring(L, 2); + if (cm_load(m.get(), dir, runtime_config) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + lua_settop(L, 1); + return 1; +} + +/// \brief This is the machine:get_default_machine_config() method implementation +/// \param L Lua state. +static int machine_obj_index_get_default_config(lua_State *L) { + auto &m = clua_check>(L, 1); + const char *config = nullptr; + if (cm_get_default_config(m.get(), &config) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + clua_push_json_table(L, config); + return 1; +} + +/// \brief This is the machine:get_reg_address() method implementation. +/// \param L Lua state. +static int machine_obj_index_get_reg_address(lua_State *L) { + auto &m = clua_check>(L, 1); + uint64_t reg_address{}; + const cm_reg reg = clua_check_cm_proc_reg(L, 2); + if (cm_get_reg_address(m.get(), reg, ®_address) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + lua_pushinteger(L, static_cast(reg_address)); + return 1; +} + +/// \brief This is the machine:verify_step_uarch() method implementation. +/// \param L Lua state. +static int machine_obj_index_verify_step_uarch(lua_State *L) { + lua_settop(L, 4); + auto &m = clua_check>(L, 1); + cm_hash root_hash{}; + clua_check_cm_hash(L, 2, &root_hash); + const char *log = clua_check_schemed_json_string(L, 3, "AccessLog"); + cm_hash target_hash{}; + clua_check_cm_hash(L, 4, &target_hash); + if (cm_verify_step_uarch(m.get(), &root_hash, log, &target_hash) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + return 0; +} + +/// \brief This is the machine:verify_reset_uarch() method implementation. +/// \param L Lua state. +static int machine_obj_index_verify_reset_uarch(lua_State *L) { + lua_settop(L, 4); + auto &m = clua_check>(L, 1); + cm_hash root_hash{}; + clua_check_cm_hash(L, 2, &root_hash); + const char *log = clua_check_schemed_json_string(L, 3, "AccessLog"); + cm_hash target_hash{}; + clua_check_cm_hash(L, 4, &target_hash); + if (cm_verify_reset_uarch(m.get(), &root_hash, log, &target_hash) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + return 0; +} + +/// \brief This is the machine:verify_send_cmio_response() method implementation. +/// \param L Lua state. +static int machine_obj_index_verify_send_cmio_response(lua_State *L) { + lua_settop(L, 6); + auto &m = clua_check>(L, 1); + const uint16_t reason = static_cast(luaL_checkinteger(L, 2)); + size_t length{0}; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + const auto *data = reinterpret_cast(luaL_checklstring(L, 3, &length)); + cm_hash root_hash{}; + clua_check_cm_hash(L, 4, &root_hash); + const char *log = clua_check_schemed_json_string(L, 5, "AccessLog"); + cm_hash target_hash{}; + clua_check_cm_hash(L, 6, &target_hash); + if (cm_verify_send_cmio_response(m.get(), reason, data, length, &root_hash, log, &target_hash) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + return 0; +} + +/// \brief This is the machine:swap() method implementation. +/// \param L Lua state. +static int machine_obj_index_swap(lua_State *L) { + auto &m1 = clua_check>(L, 1); + auto &m2 = clua_check>(L, 2); + std::swap(m1.get(), m2.get()); + return 0; +} + /// \brief Contents of the machine object metatable __index table. static const auto machine_obj_index = cartesi::clua_make_luaL_Reg_array({ + {"is_empty", machine_obj_index_is_empty}, + {"create", machine_obj_index_create}, + {"load", machine_obj_index_load}, {"get_proof", machine_obj_index_get_proof}, {"get_initial_config", machine_obj_index_get_initial_config}, + {"get_runtime_config", machine_obj_index_get_runtime_config}, + {"set_runtime_config", machine_obj_index_set_runtime_config}, {"get_root_hash", machine_obj_index_get_root_hash}, {"read_reg", machine_obj_index_read_reg}, {"read_uarch_cycle", machine_obj_index_read_uarch_cycle}, @@ -528,9 +1141,6 @@ static const auto machine_obj_index = cartesi::clua_make_luaL_Reg_array({ {"write_virtual_memory", machine_obj_index_write_virtual_memory}, {"translate_virtual_address", machine_obj_index_translate_virtual_address}, {"replace_memory_range", machine_obj_index_replace_memory_range}, - {"snapshot", machine_obj_index_snapshot}, - {"commit", machine_obj_index_commit}, - {"rollback", machine_obj_index_rollback}, {"destroy", machine_obj_index_destroy}, {"read_uarch_halt_flag", machine_obj_index_read_uarch_halt_flag}, {"set_uarch_halt_flag", machine_obj_index_set_uarch_halt_flag}, @@ -540,18 +1150,64 @@ static const auto machine_obj_index = cartesi::clua_make_luaL_Reg_array({ {"receive_cmio_request", machine_obj_index_receive_cmio_request}, {"send_cmio_response", machine_obj_index_send_cmio_response}, {"log_send_cmio_response", machine_obj_index_log_send_cmio_response}, + {"get_default_config", machine_obj_index_get_default_config}, + {"get_reg_address", machine_obj_index_get_reg_address}, + {"verify_step_uarch", machine_obj_index_verify_step_uarch}, + {"verify_reset_uarch", machine_obj_index_verify_reset_uarch}, + {"verify_send_cmio_response", machine_obj_index_verify_send_cmio_response}, + {"swap", machine_obj_index_swap}, +}); + +/// \brief This is the class() constructor implementation. +/// \param L Lua state. +static int machine_meta_call(lua_State *L) { + // This receives the source machine that is being "called" as the first argument, + // either a config or a directory as second argument, and an optional runtime config as third argument. + lua_settop(L, 3); + auto &m = clua_check>(L, 1); // source machine + // We could be creating a local machine or a remote machine. + // The type is decided by source machine, which will be a cm_machine created with + // either cm_new(nullptr, ...) or with cm_jsonrpc_connect_server/spawn_server/fork_server + // When we call cm_new(m.get(), ...), it creates a new empty object from the same underlying type as m.get(). + auto &new_m = clua_push_to(L, clua_managed_cm_ptr(nullptr)); + if (cm_new(m.get(), &new_m.get()) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + const char *runtime_config = !lua_isnil(L, 3) ? clua_check_json_string(L, 3) : nullptr; + // Create or load a machine depending on the type of the first argument + if (!lua_isstring(L, 2)) { + const char *config = clua_check_json_string(L, 2); + if (cm_create(new_m.get(), config, runtime_config) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + } else { + const char *dir = luaL_checkstring(L, 2); + if (cm_load(new_m.get(), dir, runtime_config) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + } + return 1; +} + +/// \brief Contents of the machine class metatable __index table. +static const auto machine_meta = cartesi::clua_make_luaL_Reg_array({ + {"__call", machine_meta_call}, }); int clua_i_virtual_machine_init(lua_State *L, int ctxidx) { + clua_createnewtype>(L, ctxidx); + clua_createnewtype>(L, ctxidx); + clua_createnewtype>(L, ctxidx); if (!clua_typeexists>(L, ctxidx)) { clua_createtype>(L, "cartesi machine object", ctxidx); clua_setmethods>(L, machine_obj_index.data(), 0, ctxidx); + clua_setmetamethods>(L, machine_meta.data(), 0, ctxidx); } - return 1; + return 0; } int clua_i_virtual_machine_export(lua_State *L, int ctxidx) { - clua_i_virtual_machine_init(L, ctxidx); // cartesi + clua_i_virtual_machine_init(L, ctxidx); return 0; } diff --git a/src/clua-i-virtual-machine.h b/src/clua-i-virtual-machine.h index 54d7bd416..ef13788fa 100644 --- a/src/clua-i-virtual-machine.h +++ b/src/clua-i-virtual-machine.h @@ -18,21 +18,19 @@ #define CLUA_I_VIRTUAL_MACHINE_H #include +#include +#include -extern "C" { -#include -} - +#include "clua.h" #include "i-virtual-machine.h" +#include "json-util.h" +#include "machine-c-api.h" /// \file /// \brief Cartesi machine Lua interface namespace cartesi { -/// \brief Type used to represent virtual machine objects in Lua -using clua_i_virtual_machine_ptr = std::unique_ptr; - /// \brief Initialize Cartesi machine Lua interface /// \param L Lua state /// \param ctxidx Index of Clua context @@ -43,6 +41,162 @@ int clua_i_virtual_machine_init(lua_State *L, int ctxidx); /// \param ctxidx Index of Clua context int clua_i_virtual_machine_export(lua_State *L, int ctxidx); +/// \brief Create overloaded deleters for C API objects +template +void clua_delete(T *ptr); + +/// \brief Deleter for C data buffer +template <> +void clua_delete(unsigned char *ptr); + +/// \brief Deleter for machine +template <> +void clua_delete(cm_machine *ptr); + +/// \brief Deleter for string +template <> +void clua_delete(std::string *ptr); + +/// \brief Deleter for JSON +template <> +void clua_delete(nlohmann::json *ptr); + +// clua_managed_cm_ptr is a smart pointer, +// however we don't use all its functionally, therefore we exclude it from code coverage. +// LCOV_EXCL_START +template +class clua_managed_cm_ptr final { +public: + clua_managed_cm_ptr() : m_ptr{nullptr} {} + + explicit clua_managed_cm_ptr(T *ptr) : m_ptr{ptr} {} + + explicit clua_managed_cm_ptr(clua_managed_cm_ptr &&other) noexcept : m_ptr{other.m_ptr} { + other.m_ptr = nullptr; + } + + clua_managed_cm_ptr &operator=(clua_managed_cm_ptr &&other) noexcept { + reset(); + std::swap(m_ptr, other.m_ptr); + return *this; + }; + + ~clua_managed_cm_ptr() { + reset(); + } + + clua_managed_cm_ptr(const clua_managed_cm_ptr &other) = delete; + void operator=(const clua_managed_cm_ptr &other) = delete; + + T *operator->() const noexcept { + return m_ptr; + } + + T &operator*() const { + return *m_ptr; + } + + void reset(T *ptr = nullptr) { + clua_delete(m_ptr); // use overloaded deleter + m_ptr = ptr; + } + + T *release(void) noexcept { + auto *tmp_ptr = m_ptr; + m_ptr = nullptr; + return tmp_ptr; + } + + T *&get(void) noexcept { // return reference to internal ptr + return m_ptr; + } + + T *get(void) const noexcept { + return m_ptr; + } + +private: + T *m_ptr; +}; +// LCOV_EXCL_STOP + +/// \brief Allocates a new type, pushes its reference into the Lua stack and returns its pointer. +/// \param L Lua state +/// \param value Initial value +/// \param ctxidx Index (or pseudo-index) of clua context +/// \returns The value pointer, valid until its reference is removed from the Lua stack. +/// \details The value is marked to-be-closed when popped from the Lua stack. +/// This follow lua_toclose semantics (check Lua 5.4 manual), +/// therefore the stack index can only be removed via lua_pop (e.g. don't use lua_remove). +template +T *clua_push_new_managed_toclose_ptr(lua_State *L, T &&value, int ctxidx = lua_upvalueindex(1)) { + auto &managed_value = clua_push_to(L, clua_managed_cm_ptr(new T(std::forward(value))), ctxidx); + // ??(edubart): Unfortunately Lua 5.4.4 (default on Ubuntu 22.04) has a bug that causes a crash + // when using lua_settop with lua_toclose, it was fixed only in Lua 5.4.5 in + // https://github.com/lua/lua/commit/196bb94d66e727e0aec053a0276c3ad701500762 . + // Without lua_toclose call, reference will be only collected by the GC (non deterministic). +#if LUA_VERSION_RELEASE_NUM > 50404 + lua_toclose(L, -1); +#endif + return managed_value.get(); +} + +/// \brief Returns a register selector from Lua +/// \param L Lua state +/// \param idx Index in stack +/// \returns C API register selector. Lua argument error if unknown +cm_reg clua_check_cm_proc_reg(lua_State *L, int idx); + +/// \brief Pushes a C api hash object to the Lua stack +/// \param L Lua state +/// \param hash Hash to be pushed +void clua_push_cm_hash(lua_State *L, const cm_hash *hash); + +/// \brief Return C hash from Lua +/// \param L Lua state +/// \param idx Index in stack +/// \param c_hash Receives hash +void clua_check_cm_hash(lua_State *L, int idx, cm_hash *c_hash); + +/// \brief Replaces a Lua table with its JSON string representation and returns the string +/// \param L Lua state +/// \param idx Lua table stack index which will be converted to a Lua string +/// \param indent JSON indentation when converting it to a string +/// \param ctxidx Index (or pseudo-index) of clua context +/// \param schema Schema for the table +/// \param schema_dict Dictionary containing schema for all types +/// \returns It traverses the Lua value while converting to a JSON object +/// \details In case the Lua valua is already a string, it just returns it +const char *clua_check_json_string(lua_State *L, int idx, int indent = -1, int ctxidx = lua_upvalueindex(1), + const nlohmann::json &schema = nlohmann::json(), const nlohmann::json &schema_dict = nlohmann::json()); + +/// \brief Parses a JSON from a string and pushes it as a Lua table +/// \param L Lua state +/// \param s JSON string +/// \param ctxidx Index (or pseudo-index) of clua context +/// \param schema Schema for the table +/// \param schema_dict Dictionary containing schema for all types +/// \returns It traverses the JSON object while converting to a Lua object +void clua_push_json_table(lua_State *L, const char *s, int ctxidx = lua_upvalueindex(1), + const nlohmann::json &schema = nlohmann::json(), const nlohmann::json &schema_dict = nlohmann::json()); + +/// \brief Replaces a Lua table with its JSON string representation and returns the string (schemed version) +/// \param L Lua state +/// \param idx Lua table stack index which will be converted to a Lua string +/// \param schema_name Schema name to be used while converting the table +/// \param ctxidx Index (or pseudo-index) of clua context +const char *clua_check_schemed_json_string(lua_State *L, int idx, const std::string &schema_name, + int ctxidx = lua_upvalueindex(1)); + +/// \brief Parses a JSON from a string and pushes it as a Lua table (schemed version) +/// \param L Lua state +/// \param s JSON string +/// \param idx Lua table stack index which will be converted to a Lua string +/// \param schema_name Schema name to be used while converting the table +/// \param ctxidx Index (or pseudo-index) of clua context +void clua_push_schemed_json_table(lua_State *L, const char *s, const std::string &schema_name, + int ctxidx = lua_upvalueindex(1)); + } // namespace cartesi #endif diff --git a/src/clua-jsonrpc-machine.cpp b/src/clua-jsonrpc-machine.cpp deleted file mode 100644 index db498be20..000000000 --- a/src/clua-jsonrpc-machine.cpp +++ /dev/null @@ -1,332 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT ANY -// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#include -#include -#include -#include - -#include "clua-i-virtual-machine.h" -#include "clua-jsonrpc-machine.h" -#include "clua-machine-util.h" -#include "clua.h" -#include "jsonrpc-machine-c-api.h" -#include "machine-c-api.h" - -namespace cartesi { - -/// \brief Deleter for C api jsonrpc connection -template <> -void clua_delete(cm_jsonrpc_connection *ptr) { - cm_jsonrpc_release_connection(ptr); -} - -/// \brief This is the machine.get_default_machine_config() -/// static method implementation. -static int jsonrpc_machine_class_get_default_config(lua_State *L) { - const int conidx = lua_upvalueindex(1); - const int ctxidx = lua_upvalueindex(2); - auto &c = clua_check>(L, conidx, ctxidx); - const char *config = nullptr; - if (cm_jsonrpc_get_default_config(c.get(), &config) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - clua_push_json_table(L, config, ctxidx); - return 1; -} - -/// \brief This is the machine.get_reg_address() method implementation. -static int jsonrpc_machine_class_get_reg_address(lua_State *L) { - auto &c = clua_check>(L, lua_upvalueindex(1), lua_upvalueindex(2)); - uint64_t reg_address{}; - const cm_reg reg = clua_check_cm_proc_reg(L, 1); - if (cm_jsonrpc_get_reg_address(c.get(), reg, ®_address) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - lua_pushinteger(L, static_cast(reg_address)); - return 1; -} - -/// \brief This is the machine.verify_step_uarch() -/// static method implementation. -static int jsonrpc_machine_class_verify_step_uarch(lua_State *L) { - const int conidx = lua_upvalueindex(1); - const int ctxidx = lua_upvalueindex(2); - lua_settop(L, 5); - auto &c = clua_check>(L, conidx, ctxidx); - const char *log = clua_check_schemed_json_string(L, 2, "AccessLog", ctxidx); - cm_hash root_hash{}; - clua_check_cm_hash(L, 1, &root_hash); - cm_hash target_hash{}; - clua_check_cm_hash(L, 3, &target_hash); - if (cm_jsonrpc_verify_step_uarch(c.get(), &root_hash, log, &target_hash) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - return 0; -} - -/// \brief This is the machine.verify_reset_uarch() -/// static method implementation. -static int jsonrpc_machine_class_verify_reset_uarch(lua_State *L) { - const int conidx = lua_upvalueindex(1); - const int ctxidx = lua_upvalueindex(2); - lua_settop(L, 5); - auto &c = clua_check>(L, conidx, ctxidx); - const char *log = clua_check_schemed_json_string(L, 2, "AccessLog", ctxidx); - cm_hash root_hash{}; - clua_check_cm_hash(L, 1, &root_hash); - cm_hash target_hash{}; - clua_check_cm_hash(L, 3, &target_hash); - if (cm_jsonrpc_verify_reset_uarch(c.get(), &root_hash, log, &target_hash) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - return 0; -} - -/// \brief This is the machine.verify_send_cmio_response() -/// static method implementation. -static int jsonrpc_machine_class_verify_send_cmio_response(lua_State *L) { - const int conidx = lua_upvalueindex(1); - const int ctxidx = lua_upvalueindex(2); - lua_settop(L, 6); - auto &c = clua_check>(L, conidx, ctxidx); - const uint16_t reason = static_cast(luaL_checkinteger(L, 1)); - size_t length{0}; - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - const auto *data = reinterpret_cast(luaL_checklstring(L, 2, &length)); - const char *log = clua_check_schemed_json_string(L, 4, "AccessLog", ctxidx); - cm_hash root_hash{}; - clua_check_cm_hash(L, 3, &root_hash); - cm_hash target_hash{}; - clua_check_cm_hash(L, 5, &target_hash); - if (cm_jsonrpc_verify_send_cmio_response(c.get(), reason, data, length, &root_hash, log, &target_hash) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - return 0; -} - -/// \brief Contents of the machine class metatable __index table. -static const auto jsonrpc_machine_static_methods = cartesi::clua_make_luaL_Reg_array({ - {"get_default_config", jsonrpc_machine_class_get_default_config}, - {"get_reg_address", jsonrpc_machine_class_get_reg_address}, - {"verify_step_uarch", jsonrpc_machine_class_verify_step_uarch}, - {"verify_reset_uarch", jsonrpc_machine_class_verify_reset_uarch}, - {"verify_send_cmio_response", jsonrpc_machine_class_verify_send_cmio_response}, -}); - -/// \brief Prints a JSONRPC machine class -/// \param L Lua state. -static int jsonrpc_machine_tostring(lua_State *L) { - lua_pushliteral(L, "JSONRPC machine class"); - return 1; -} - -/// \brief This is the cartesi.machine() constructor implementation. -/// \param L Lua state. -static int jsonrpc_machine_ctor(lua_State *L) { - const int conidx = lua_upvalueindex(1); - const int ctxidx = lua_upvalueindex(2); - lua_settop(L, 4); - auto &c = clua_check>(L, conidx, ctxidx); - auto &m = clua_push_to(L, clua_managed_cm_ptr(nullptr), ctxidx); - const char *runtime_config = !lua_isnil(L, 3) ? clua_check_json_string(L, 3, -1, ctxidx) : nullptr; - if (!lua_isstring(L, 2)) { - const char *config = clua_check_json_string(L, 2, -1, ctxidx); - if (cm_jsonrpc_create_machine(c.get(), lua_toboolean(L, 4), config, runtime_config, &m.get()) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - } else { - const char *dir = luaL_checkstring(L, 2); - if (cm_jsonrpc_load_machine(c.get(), lua_toboolean(L, 4), dir, runtime_config, &m.get()) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - } - return 1; -} - -/// \brief Contents of the jsonrpc machine class metatable. -static const auto jsonrpc_machine_class_meta = cartesi::clua_make_luaL_Reg_array({ - {"__call", jsonrpc_machine_ctor}, - {"__tostring", jsonrpc_machine_tostring}, -}); - -/// \brief This is the connection.get_machine() static method implementation. -static int jsonrpc_connection_class_get_machine(lua_State *L) { - lua_settop(L, 1); - auto &c = clua_check>(L, lua_upvalueindex(1), lua_upvalueindex(2)); - const int ctxidx = lua_upvalueindex(2); - auto &m = clua_push_to(L, clua_managed_cm_ptr(nullptr), ctxidx); - if (cm_jsonrpc_get_machine(c.get(), lua_toboolean(L, 1), &m.get()) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - return 1; -} - -/// \brief This is the connection.get_server_version() static method implementation. -static int jsonrpc_connection_class_get_server_version(lua_State *L) { - const int conidx = lua_upvalueindex(1); - const int ctxidx = lua_upvalueindex(2); - auto &c = clua_check>(L, conidx, ctxidx); - const char *version = nullptr; - if (cm_jsonrpc_get_server_version(c.get(), &version) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - clua_push_json_table(L, version, ctxidx); - return 1; -} - -/// \brief This is the connection.rebind_server() static method implementation. -static int jsonrpc_connection_class_rebind_server(lua_State *L) { - auto &c = clua_check>(L, lua_upvalueindex(1), lua_upvalueindex(2)); - const char *address = luaL_checkstring(L, 1); - const char *new_address = nullptr; - if (cm_jsonrpc_rebind_server(c.get(), address, &new_address) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - if (new_address) { - lua_pushstring(L, new_address); - } else { - lua_pushnil(L); - } - return 1; -} - -/// \brief This is the connection.fork_server() static method implementation. -static int jsonrpc_connection_class_fork_server(lua_State *L) { - auto &c = clua_check>(L, lua_upvalueindex(1), lua_upvalueindex(2)); - const char *address = nullptr; - int32_t pid = 0; - if (cm_jsonrpc_fork_server(c.get(), &address, &pid) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - lua_pushstring(L, address); - lua_pushinteger(L, pid); - return 2; -} - -/// \brief This is the connection.shutdown_server() method implementation. -static int jsonrpc_connection_class_shutdown_server(lua_State *L) { - auto &c = clua_check>(L, lua_upvalueindex(1), lua_upvalueindex(2)); - if (cm_jsonrpc_shutdown_server(c.get()) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - c.release(); - return 0; -} - -/// \brief JSONRPC connection static methods -static const auto jsonrpc_connection_static_methods = cartesi::clua_make_luaL_Reg_array({ - {"get_machine", jsonrpc_connection_class_get_machine}, - {"get_server_version", jsonrpc_connection_class_get_server_version}, - {"fork_server", jsonrpc_connection_class_fork_server}, - {"rebind_server", jsonrpc_connection_class_rebind_server}, - {"shutdown_server", jsonrpc_connection_class_shutdown_server}, -}); - -/// \brief Takes underlying cm_jsonrpc_connection in top of stack and encapsulates it in its Lua interface -static void wrap_jsonrpc_connection(lua_State *L) { - lua_newtable(L); // ccon luacon - lua_newtable(L); // ccon luacon mtab - lua_pushvalue(L, -3); // ccon luacon mtab ccon - lua_pushvalue(L, lua_upvalueindex(1)); // ccon luacon mtab ccon cluactx - luaL_setfuncs(L, jsonrpc_machine_static_methods.data(), 2); // ccon luacon mtab - lua_newtable(L); // ccon luacon mtab mmeta - lua_pushvalue(L, -4); // ccon luacon mtab mmeta ccon - lua_pushvalue(L, lua_upvalueindex(1)); // ccon luacon mtab mmeta ccon cluactx - luaL_setfuncs(L, jsonrpc_machine_class_meta.data(), 2); // ccon luacon mtab mmeta - lua_setmetatable(L, -2); // ccon luacon mtab - lua_setfield(L, -2, "machine"); // ccon luacon - lua_pushvalue(L, -2); // ccon luacon ccon - lua_pushvalue(L, lua_upvalueindex(1)); // ccon luacon ccon cluactx - luaL_setfuncs(L, jsonrpc_connection_static_methods.data(), 2); // ccon luacon - lua_insert(L, -2); // luacon ccon - lua_pop(L, 1); // luacon -} - -/// \brief This is the jsonrpc.connect() method implementation. -static int mod_connect(lua_State *L) { - // create and push the underlying cm_jsonrpc_connection - const char *address = luaL_checkstring(L, 1); - auto detach_server = lua_toboolean(L, 2); - auto &c = clua_push_to(L, clua_managed_cm_ptr(nullptr)); - if (cm_jsonrpc_connect(address, detach_server, &c.get()) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - // wrap it into its Lua interface - wrap_jsonrpc_connection(L); - return 1; -} - -/// \brief This is the jsonrpc.connect() method implementation. -static int mod_spawn_server(lua_State *L) { - const char *address = luaL_checkstring(L, 1); - auto detach_server = lua_toboolean(L, 2); - // create and push the underlying cm_jsonrpc_connection - auto &c = clua_push_to(L, clua_managed_cm_ptr(nullptr)); - const char *bound_address = nullptr; - int32_t pid = 0; - if (cm_jsonrpc_spawn_server(address, detach_server, &c.get(), &bound_address, &pid) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - // wrap it into its Lua interface - wrap_jsonrpc_connection(L); - lua_pushstring(L, bound_address); - lua_pushinteger(L, pid); - return 3; -} - -/// \brief Contents of the jsonrpc module. -static const auto mod = cartesi::clua_make_luaL_Reg_array({ - {"connect", mod_connect}, - {"spawn_server", mod_spawn_server}, -}); - -int clua_jsonrpc_machine_init(lua_State *L, int ctxidx) { - clua_createnewtype>(L, ctxidx); - clua_createnewtype>(L, ctxidx); - clua_createnewtype>(L, ctxidx); - clua_createnewtype>(L, ctxidx); - return 1; -} - -/// \brief This is the machine:destroy() method implementation for remote machines. -/// \param L Lua state. -static int jsonrpc_machine_obj_index_destroy(lua_State *L) { - auto &m = clua_check>(L, 1); - if (cm_jsonrpc_destroy_machine(m.get()) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - m.release(); - return 0; -} - -/// \brief Contents of the machine object metatable __index table. -static const auto jsonrpc_machine_obj_index = cartesi::clua_make_luaL_Reg_array({ - {"destroy", jsonrpc_machine_obj_index_destroy}, -}); - -int clua_jsonrpc_machine_export(lua_State *L, int ctxidx) { - const int ctxabsidx = lua_absindex(L, ctxidx); - // jsonrpc - clua_i_virtual_machine_export(L, ctxabsidx); // jsonrpc - clua_setmethods>(L, jsonrpc_machine_obj_index.data(), 0, ctxabsidx); - clua_jsonrpc_machine_init(L, ctxabsidx); // jsonrpc - lua_pushvalue(L, ctxabsidx); // jsonrpc cluactx - luaL_setfuncs(L, mod.data(), 1); // jsonrpc - return 0; -} - -} // namespace cartesi diff --git a/src/clua-jsonrpc-machine.h b/src/clua-jsonrpc-machine.h deleted file mode 100644 index 5bcd5772f..000000000 --- a/src/clua-jsonrpc-machine.h +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT ANY -// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#ifndef CLUA_JSONRPC_MACHINE_H -#define CLUA_JSONRPC_MACHINE_H - -extern "C" { -#include -} - -/// \file -/// \brief Remote Cartesi machine Lua interface - -namespace cartesi { - -/// \brief Initialize remote Cartesi machine Lua interface -/// \param L Lua state -/// \param ctxidx Index of clua context -int clua_jsonrpc_machine_init(lua_State *L, int ctxidx); - -/// \brief Exports symbols to table on top of Lua stack -/// \param L Lua state -/// \param ctxidx Index of clua context -int clua_jsonrpc_machine_export(lua_State *L, int ctxidx); - -} // namespace cartesi - -#endif diff --git a/src/clua-machine-util.cpp b/src/clua-machine-util.cpp deleted file mode 100644 index e5fc92b78..000000000 --- a/src/clua-machine-util.cpp +++ /dev/null @@ -1,514 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT ANY -// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "base64.h" -#include "clua-machine-util.h" -#include "machine-c-api.h" - -namespace cartesi { - -template <> -void clua_delete(unsigned char *ptr) { // NOLINT(readability-non-const-parameter) - delete[] ptr; -} - -template <> -void clua_delete(cm_machine *ptr) { - cm_destroy(ptr); // this call should never fail -} - -template <> -void clua_delete(std::string *ptr) { - delete ptr; -} - -template <> -void clua_delete(nlohmann::json *ptr) { - delete ptr; -} - -cm_reg clua_check_cm_proc_reg(lua_State *L, int idx) try { - /// \brief Mapping between register names and C API constants - const static std::unordered_map g_cm_proc_reg_name = { - // clang-format off - {"x0", CM_REG_X0}, - {"x1", CM_REG_X1}, - {"x2", CM_REG_X2}, - {"x3", CM_REG_X3}, - {"x4", CM_REG_X4}, - {"x5", CM_REG_X5}, - {"x6", CM_REG_X6}, - {"x7", CM_REG_X7}, - {"x8", CM_REG_X8}, - {"x9", CM_REG_X9}, - {"x10", CM_REG_X10}, - {"x11", CM_REG_X11}, - {"x12", CM_REG_X12}, - {"x13", CM_REG_X13}, - {"x14", CM_REG_X14}, - {"x15", CM_REG_X15}, - {"x16", CM_REG_X16}, - {"x17", CM_REG_X17}, - {"x18", CM_REG_X18}, - {"x19", CM_REG_X19}, - {"x20", CM_REG_X20}, - {"x21", CM_REG_X21}, - {"x22", CM_REG_X22}, - {"x23", CM_REG_X23}, - {"x24", CM_REG_X24}, - {"x25", CM_REG_X25}, - {"x26", CM_REG_X26}, - {"x27", CM_REG_X27}, - {"x28", CM_REG_X28}, - {"x29", CM_REG_X29}, - {"x30", CM_REG_X30}, - {"x31", CM_REG_X31}, - {"f0", CM_REG_F0}, - {"f1", CM_REG_F1}, - {"f2", CM_REG_F2}, - {"f3", CM_REG_F3}, - {"f4", CM_REG_F4}, - {"f5", CM_REG_F5}, - {"f6", CM_REG_F6}, - {"f7", CM_REG_F7}, - {"f8", CM_REG_F8}, - {"f9", CM_REG_F9}, - {"f10", CM_REG_F10}, - {"f11", CM_REG_F11}, - {"f12", CM_REG_F12}, - {"f13", CM_REG_F13}, - {"f14", CM_REG_F14}, - {"f15", CM_REG_F15}, - {"f16", CM_REG_F16}, - {"f17", CM_REG_F17}, - {"f18", CM_REG_F18}, - {"f19", CM_REG_F19}, - {"f20", CM_REG_F20}, - {"f21", CM_REG_F21}, - {"f22", CM_REG_F22}, - {"f23", CM_REG_F23}, - {"f24", CM_REG_F24}, - {"f25", CM_REG_F25}, - {"f26", CM_REG_F26}, - {"f27", CM_REG_F27}, - {"f28", CM_REG_F28}, - {"f29", CM_REG_F29}, - {"f30", CM_REG_F30}, - {"f31", CM_REG_F31}, - {"pc", CM_REG_PC}, - {"fcsr", CM_REG_FCSR}, - {"mvendorid", CM_REG_MVENDORID}, - {"marchid", CM_REG_MARCHID}, - {"mimpid", CM_REG_MIMPID}, - {"mcycle", CM_REG_MCYCLE}, - {"icycleinstret", CM_REG_ICYCLEINSTRET}, - {"mstatus", CM_REG_MSTATUS}, - {"mtvec", CM_REG_MTVEC}, - {"mscratch", CM_REG_MSCRATCH}, - {"mepc", CM_REG_MEPC}, - {"mcause", CM_REG_MCAUSE}, - {"mtval", CM_REG_MTVAL}, - {"misa", CM_REG_MISA}, - {"mie", CM_REG_MIE}, - {"mip", CM_REG_MIP}, - {"medeleg", CM_REG_MEDELEG}, - {"mideleg", CM_REG_MIDELEG}, - {"mcounteren", CM_REG_MCOUNTEREN}, - {"menvcfg", CM_REG_MENVCFG}, - {"stvec", CM_REG_STVEC}, - {"sscratch", CM_REG_SSCRATCH}, - {"sepc", CM_REG_SEPC}, - {"scause", CM_REG_SCAUSE}, - {"stval", CM_REG_STVAL}, - {"satp", CM_REG_SATP}, - {"scounteren", CM_REG_SCOUNTEREN}, - {"senvcfg", CM_REG_SENVCFG}, - {"ilrsc", CM_REG_ILRSC}, - {"iflags", CM_REG_IFLAGS}, - {"iflags_prv", CM_REG_IFLAGS_PRV}, - {"iflags_x", CM_REG_IFLAGS_X}, - {"iflags_y", CM_REG_IFLAGS_Y}, - {"iflags_h", CM_REG_IFLAGS_H}, - {"iunrep", CM_REG_IUNREP}, - {"clint_mtimecmp", CM_REG_CLINT_MTIMECMP}, - {"plic_girqpend", CM_REG_PLIC_GIRQPEND}, - {"plic_girqsrvd", CM_REG_PLIC_GIRQSRVD}, - {"htif_tohost", CM_REG_HTIF_TOHOST}, - {"htif_tohost_dev", CM_REG_HTIF_TOHOST_DEV}, - {"htif_tohost_cmd", CM_REG_HTIF_TOHOST_CMD}, - {"htif_tohost_reason", CM_REG_HTIF_TOHOST_REASON}, - {"htif_tohost_data", CM_REG_HTIF_TOHOST_DATA}, - {"htif_fromhost", CM_REG_HTIF_FROMHOST}, - {"htif_fromhost_dev", CM_REG_HTIF_FROMHOST_DEV}, - {"htif_fromhost_cmd", CM_REG_HTIF_FROMHOST_CMD}, - {"htif_fromhost_reason", CM_REG_HTIF_FROMHOST_REASON}, - {"htif_fromhost_data", CM_REG_HTIF_FROMHOST_DATA}, - {"htif_ihalt", CM_REG_HTIF_IHALT}, - {"htif_iconsole", CM_REG_HTIF_ICONSOLE}, - {"htif_iyield", CM_REG_HTIF_IYIELD}, - {"uarch_x0", CM_REG_UARCH_X0}, - {"uarch_x1", CM_REG_UARCH_X1}, - {"uarch_x2", CM_REG_UARCH_X2}, - {"uarch_x3", CM_REG_UARCH_X3}, - {"uarch_x4", CM_REG_UARCH_X4}, - {"uarch_x5", CM_REG_UARCH_X5}, - {"uarch_x6", CM_REG_UARCH_X6}, - {"uarch_x7", CM_REG_UARCH_X7}, - {"uarch_x8", CM_REG_UARCH_X8}, - {"uarch_x9", CM_REG_UARCH_X9}, - {"uarch_x10", CM_REG_UARCH_X10}, - {"uarch_x11", CM_REG_UARCH_X11}, - {"uarch_x12", CM_REG_UARCH_X12}, - {"uarch_x13", CM_REG_UARCH_X13}, - {"uarch_x14", CM_REG_UARCH_X14}, - {"uarch_x15", CM_REG_UARCH_X15}, - {"uarch_x16", CM_REG_UARCH_X16}, - {"uarch_x17", CM_REG_UARCH_X17}, - {"uarch_x18", CM_REG_UARCH_X18}, - {"uarch_x19", CM_REG_UARCH_X19}, - {"uarch_x20", CM_REG_UARCH_X20}, - {"uarch_x21", CM_REG_UARCH_X21}, - {"uarch_x22", CM_REG_UARCH_X22}, - {"uarch_x23", CM_REG_UARCH_X23}, - {"uarch_x24", CM_REG_UARCH_X24}, - {"uarch_x25", CM_REG_UARCH_X25}, - {"uarch_x26", CM_REG_UARCH_X26}, - {"uarch_x27", CM_REG_UARCH_X27}, - {"uarch_x28", CM_REG_UARCH_X28}, - {"uarch_x29", CM_REG_UARCH_X29}, - {"uarch_x30", CM_REG_UARCH_X30}, - {"uarch_x31", CM_REG_UARCH_X31}, - {"uarch_pc", CM_REG_UARCH_PC}, - {"uarch_cycle", CM_REG_UARCH_CYCLE}, - {"uarch_halt_flag", CM_REG_UARCH_HALT_FLAG}, - // clang-format on - }; - const char *name = luaL_checkstring(L, idx); - auto got = g_cm_proc_reg_name.find(name); - if (got == g_cm_proc_reg_name.end()) { - luaL_argerror(L, idx, "unknown register"); - } - return got->second; -} catch (const std::exception &e) { - luaL_error(L, "%s", e.what()); - return CM_REG_UNKNOWN; // will not be reached -} catch (...) { - luaL_error(L, "unknown error with register type conversion"); - return CM_REG_UNKNOWN; // will not be reached -} - -void clua_check_cm_hash(lua_State *L, int idx, cm_hash *c_hash) { - if (lua_isstring(L, idx)) { - size_t len = 0; - const char *data = lua_tolstring(L, idx, &len); - if (len != sizeof(cm_hash)) { - luaL_error(L, "hash length must be 32 bytes"); - } - memcpy(c_hash, data, sizeof(cm_hash)); - } else { - luaL_error(L, "hash length must be 32 bytes"); - } -} - -void clua_push_cm_hash(lua_State *L, const cm_hash *hash) { - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - lua_pushlstring(L, reinterpret_cast(hash), CM_HASH_SIZE); -} - -static int64_t clua_get_array_table_len(lua_State *L, int tabidx) { - if (!lua_istable(L, tabidx)) { - return -1; - } - int64_t len = 0; - lua_pushvalue(L, tabidx); // push table - lua_pushnil(L); // push key - while (lua_next(L, -2)) { // replace key, push value - if (!lua_isinteger(L, -2)) { // non integer key, not an array - lua_pop(L, 3); - return -1; - } - const int64_t i = lua_tointeger(L, -2); - if (i <= 0) { // invalid index, not an array - lua_pop(L, 3); - return -1; - } - len = std::max(i, len); - lua_pop(L, 1); // pop value - } - lua_pop(L, 1); // pop key - return len; -} - -static const nlohmann::json &clua_get_json_field_schema(const std::string_view field_name, const nlohmann::json &schema, - const nlohmann::json &schema_dict) { - static const nlohmann::json empty_schema; - if (!schema.contains(field_name)) { - return empty_schema; - } - const auto &type_name = schema.at(field_name).template get(); - return schema_dict.at(type_name); -} - -static nlohmann::json &clua_push_json_value_ref(lua_State *L, int idx, int ctxidx, const nlohmann::json &schema, - const nlohmann::json &schema_dict) { - nlohmann::json &j = *clua_push_new_managed_toclose_ptr(L, nlohmann::json(), ctxidx); - idx -= idx < 0 ? 1 : 0; // adjust offset after pushing j reference - switch (lua_type(L, idx)) { - case LUA_TTABLE: { - const int64_t len = clua_get_array_table_len(L, idx); - if (len >= 0) { // array - j = nlohmann::json::array(); - const auto &field_schema = clua_get_json_field_schema("items", schema, schema_dict); - for (int64_t i = 1; i <= len; ++i) { - lua_geti(L, idx, i); - j.push_back(clua_push_json_value_ref(L, -1, ctxidx, field_schema, schema_dict)); - lua_pop(L, 2); // pop value, child j reference - } - } else { // object - j = nlohmann::json::object(); - lua_pushvalue(L, idx); // push table - lua_pushnil(L); // push key - while (lua_next(L, -2)) { // update key, push value - if (!lua_isstring(L, -2)) { - luaL_error(L, "table maps cannot contain keys of type %s", lua_typename(L, lua_type(L, -2))); - } - const char *field_name = lua_tostring(L, -2); - const auto &field_schema = clua_get_json_field_schema(field_name, schema, schema_dict); - j[field_name] = clua_push_json_value_ref(L, -1, ctxidx, field_schema, schema_dict); - lua_pop(L, 2); // pop value, child j reference - } - lua_pop(L, 1); // pop table - } - break; - } - case LUA_TNUMBER: { - if (lua_isinteger(L, idx)) { - int64_t v = lua_tointeger(L, idx); - if (schema.is_string() && schema.template get() == "ArrayIndex") { - v -= 1; - } - j = v; - } else { // floating point - j = lua_tonumber(L, idx); - } - break; - } - case LUA_TSTRING: { - size_t len = 0; - const char *ptr = lua_tolstring(L, idx, &len); - const std::string_view data(ptr, len); - if (schema.is_string() && schema.template get() == "Base64") { - j = encode_base64(data); - } else { - j = data; - } - break; - } - case LUA_TBOOLEAN: - j = static_cast(lua_toboolean(L, idx)); - break; - case LUA_TNIL: - j = nullptr; - break; - default: - luaL_error(L, "lua value of type %s cannot be serialized to JSON", lua_typename(L, lua_type(L, idx))); - break; - } - return j; -} - -const char *clua_check_json_string(lua_State *L, int idx, int indent, int ctxidx, const nlohmann::json &schema, - const nlohmann::json &schema_dict) { - assert(idx > 0); - if (!lua_istable(L, idx)) { - luaL_error(L, "failed to parse JSON from a Lua value: expected a table but got type \"%s\"", - lua_typename(L, lua_type(L, idx))); - } - try { - const nlohmann::json &j = clua_push_json_value_ref(L, idx, ctxidx, schema, schema_dict); - std::string &s = *clua_push_new_managed_toclose_ptr(L, j.dump(indent), ctxidx); - lua_pushlstring(L, s.data(), s.size()); - lua_replace(L, idx); // replace the Lua value with its JSON string representation - lua_pop(L, 2); // pop s, j references - return luaL_checkstring(L, idx); // return the string - } catch (std::exception &e) { - luaL_error(L, "failed to parse JSON from a Lua table: %s", e.what()); - return nullptr; - } -} - -static void clua_push_json_value(lua_State *L, const nlohmann::json &j, int ctxidx, const nlohmann::json &schema, - const nlohmann::json &schema_dict) { - switch (j.type()) { - case nlohmann::json::value_t::array: { - const auto &field_schema = clua_get_json_field_schema("items", schema, schema_dict); - lua_createtable(L, static_cast(j.size()), 0); - int64_t i = 1; - for (auto it = j.begin(); it != j.end(); ++it, ++i) { - clua_push_json_value(L, *it, ctxidx, field_schema, schema_dict); - lua_rawseti(L, -2, i); - } - break; - } - case nlohmann::json::value_t::object: { - lua_createtable(L, 0, static_cast(j.size())); - for (const auto &el : j.items()) { - const auto &field_name = el.key(); - const auto &field_schema = clua_get_json_field_schema(field_name, schema, schema_dict); - clua_push_json_value(L, el.value(), ctxidx, field_schema, schema_dict); - lua_setfield(L, -2, field_name.c_str()); - } - break; - } - case nlohmann::json::value_t::string: { - const std::string_view &data = j.template get(); - if (schema.is_string() && schema.template get() == "Base64") { - lua_pushnil(L); // reserve a slot in the stack (needed because of lua_toclose semantics) - std::string &binary_data = *clua_push_new_managed_toclose_ptr(L, decode_base64(data), ctxidx); - lua_pushlstring(L, binary_data.data(), binary_data.length()); - lua_replace(L, -3); // move into the placeholder slot - lua_pop(L, 1); // pop binary_data reference - } else { - lua_pushlstring(L, data.data(), data.length()); - } - break; - } - case nlohmann::json::value_t::number_integer: { - int64_t v = j.template get(); - if (schema.is_string() && schema.template get() == "ArrayIndex") { - v += 1; - } - lua_pushinteger(L, v); - break; - } - case nlohmann::json::value_t::number_unsigned: { - int64_t v = static_cast(j.template get()); - if (schema.is_string() && schema.template get() == "ArrayIndex") { - v += 1; - } - lua_pushinteger(L, v); - break; - } - case nlohmann::json::value_t::number_float: - lua_pushnumber(L, j.template get()); - break; - case nlohmann::json::value_t::boolean: - lua_pushboolean(L, j.template get()); - break; - case nlohmann::json::value_t::null: - lua_pushnil(L); - break; - default: - luaL_error(L, "JSON value of type %s cannot be to Lua", j.type_name()); - break; - } -} - -void clua_push_json_table(lua_State *L, const char *s, int ctxidx, const nlohmann::json &schema, - const nlohmann::json &schema_dict) { - try { - lua_pushnil(L); // reserve a slot in the stack (needed because of lua_toclose semantics) - const nlohmann::json &j = *clua_push_new_managed_toclose_ptr(L, nlohmann::json::parse(s), ctxidx); - clua_push_json_value(L, j, ctxidx, schema, schema_dict); - lua_replace(L, -3); // move into the placeholder slot - lua_pop(L, 1); // pop j reference - } catch (std::exception &e) { - luaL_error(L, "failed to parse JSON from a string: %s", e.what()); - } -} - -static const nlohmann::json &clua_get_machine_schema_dict(lua_State *L) { - static nlohmann::json machine_schema_dict; - try { - if (machine_schema_dict.is_null()) { - // In order to convert Lua tables <-> JSON objects we have to define a schema - // to transform some special fields, we only care about: - // - Binary strings (translate Base64 strings in JSON to binary strings in Lua) - // - Array indexes (translate 0 based index in JSON to 1 based index in Lua) - machine_schema_dict = { - {"Base64", "Base64"}, - {"ArrayIndex", "ArrayIndex"}, - {"Base64Array", - { - {"items", "Base64"}, - }}, - {"Proof", - { - {"target_hash", "Base64"}, - {"root_hash", "Base64"}, - {"sibling_hashes", "Base64Array"}, - }}, - {"Access", - { - {"read", "Base64"}, - {"read_hash", "Base64"}, - {"written", "Base64"}, - {"written_hash", "Base64"}, - {"sibling_hashes", "Base64Array"}, - }}, - {"AccessArray", - { - {"items", "Access"}, - }}, - {"Bracket", - { - {"where", "ArrayIndex"}, - }}, - {"BracketArray", - { - {"items", "Bracket"}, - }}, - {"AccessLog", - { - {"accesses", "AccessArray"}, - {"brackets", "BracketArray"}, - }}, - }; - } - } catch (std::exception &e) { - luaL_error(L, "failed to create machine schema dictionary: %s", e.what()); - } - return machine_schema_dict; -}; - -const char *clua_check_schemed_json_string(lua_State *L, int idx, const std::string &schema_name, int ctxidx) { - const auto &machine_schema_dict = clua_get_machine_schema_dict(L); - const auto it = machine_schema_dict.find(schema_name); - if (it == machine_schema_dict.end()) { - luaL_error(L, "type \"%s\" is not defined in machine schema dictionary", schema_name.c_str()); - } - return clua_check_json_string(L, idx, -1, ctxidx, *it, machine_schema_dict); -} - -void clua_push_schemed_json_table(lua_State *L, const char *s, const std::string &schema_name, int ctxidx) { - const auto &machine_schema_dict = clua_get_machine_schema_dict(L); - const auto it = machine_schema_dict.find(schema_name); - if (it == machine_schema_dict.end()) { - luaL_error(L, "type \"%s\" is not defined in machine schema dictionary", schema_name.c_str()); - } - return clua_push_json_table(L, s, ctxidx, *it, machine_schema_dict); -} - -} // namespace cartesi diff --git a/src/clua-machine-util.h b/src/clua-machine-util.h deleted file mode 100644 index 81201201a..000000000 --- a/src/clua-machine-util.h +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT ANY -// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#ifndef CLUA_MACHINE_UTIL_H -#define CLUA_MACHINE_UTIL_H - -#include -#include - -extern "C" { -#include -} - -#include "clua.h" -#include "json-util.h" -#include "machine-c-api.h" - -/// \file -/// \brief Cartesi machine Lua interface helper functions - -namespace cartesi { - -/// \brief Create overloaded deleters for C API objects -template -void clua_delete(T *ptr); - -/// \brief Deleter for C data buffer -template <> -void clua_delete(unsigned char *ptr); - -/// \brief Deleter for machine -template <> -void clua_delete(cm_machine *ptr); - -/// \brief Deleter for string -template <> -void clua_delete(std::string *ptr); - -/// \brief Deleter for JSON -template <> -void clua_delete(nlohmann::json *ptr); - -// clua_managed_cm_ptr is a smart pointer, -// however we don't use all its functionally, therefore we exclude it from code coverage. -// LCOV_EXCL_START -template -class clua_managed_cm_ptr final { -public: - clua_managed_cm_ptr() : m_ptr{nullptr} {} - - explicit clua_managed_cm_ptr(T *ptr) : m_ptr{ptr} {} - - explicit clua_managed_cm_ptr(clua_managed_cm_ptr &&other) noexcept : m_ptr{other.m_ptr} { - other.m_ptr = nullptr; - } - - clua_managed_cm_ptr &operator=(clua_managed_cm_ptr &&other) noexcept { - reset(); - std::swap(m_ptr, other.m_ptr); - return *this; - }; - - ~clua_managed_cm_ptr() { - reset(); - } - - clua_managed_cm_ptr(const clua_managed_cm_ptr &other) = delete; - void operator=(const clua_managed_cm_ptr &other) = delete; - - T *operator->() const noexcept { - return m_ptr; - } - - T &operator*() const { - return *m_ptr; - } - - void reset(T *ptr = nullptr) { - clua_delete(m_ptr); // use overloaded deleter - m_ptr = ptr; - } - - T *release(void) noexcept { - auto *tmp_ptr = m_ptr; - m_ptr = nullptr; - return tmp_ptr; - } - - T *&get(void) noexcept { // return reference to internal ptr - return m_ptr; - } - - T *get(void) const noexcept { - return m_ptr; - } - -private: - T *m_ptr; -}; -// LCOV_EXCL_STOP - -/// \brief Allocates a new type, pushes its reference into the Lua stack and returns its pointer. -/// \param L Lua state -/// \param value Initial value -/// \param ctxidx Index (or pseudo-index) of clua context -/// \returns The value pointer, valid until its reference is removed from the Lua stack. -/// \details The value is marked to-be-closed when popped from the Lua stack. -/// This follow lua_toclose semantics (check Lua 5.4 manual), -/// therefore the stack index can only be removed via lua_pop (e.g. don't use lua_remove). -template -T *clua_push_new_managed_toclose_ptr(lua_State *L, T &&value, int ctxidx = lua_upvalueindex(1)) { - auto &managed_value = clua_push_to(L, clua_managed_cm_ptr(new T(std::forward(value))), ctxidx); - // ??(edubart): Unfortunately Lua 5.4.4 (default on Ubuntu 22.04) has a bug that causes a crash - // when using lua_settop with lua_toclose, it was fixed only in Lua 5.4.5 in - // https://github.com/lua/lua/commit/196bb94d66e727e0aec053a0276c3ad701500762 . - // Without lua_toclose call, reference will be only collected by the GC (non deterministic). -#if LUA_VERSION_RELEASE_NUM > 50404 - lua_toclose(L, -1); -#endif - return managed_value.get(); -} - -/// \brief Returns a register selector from Lua -/// \param L Lua state -/// \param idx Index in stack -/// \returns C API register selector. Lua argument error if unknown -cm_reg clua_check_cm_proc_reg(lua_State *L, int idx); - -/// \brief Pushes a C api hash object to the Lua stack -/// \param L Lua state -/// \param hash Hash to be pushed -void clua_push_cm_hash(lua_State *L, const cm_hash *hash); - -/// \brief Return C hash from Lua -/// \param L Lua state -/// \param idx Index in stack -/// \param c_hash Receives hash -void clua_check_cm_hash(lua_State *L, int idx, cm_hash *c_hash); - -/// \brief Replaces a Lua table with its JSON string representation and returns the string -/// \param L Lua state -/// \param idx Lua table stack index which will be converted to a Lua string -/// \param indent JSON indentation when converting it to a string -/// \param ctxidx Index (or pseudo-index) of clua context -/// \param schema Schema for the table -/// \param schema_dict Dictionary containing schema for all types -/// \returns It traverses the Lua value while converting to a JSON object -/// \details In case the Lua valua is already a string, it just returns it -const char *clua_check_json_string(lua_State *L, int idx, int indent = -1, int ctxidx = lua_upvalueindex(1), - const nlohmann::json &schema = nlohmann::json(), const nlohmann::json &schema_dict = nlohmann::json()); - -/// \brief Parses a JSON from a string and pushes it as a Lua table -/// \param L Lua state -/// \param s JSON string -/// \param ctxidx Index (or pseudo-index) of clua context -/// \param schema Schema for the table -/// \param schema_dict Dictionary containing schema for all types -/// \returns It traverses the JSON object while converting to a Lua object -void clua_push_json_table(lua_State *L, const char *s, int ctxidx = lua_upvalueindex(1), - const nlohmann::json &schema = nlohmann::json(), const nlohmann::json &schema_dict = nlohmann::json()); - -/// \brief Replaces a Lua table with its JSON string representation and returns the string (schemed version) -/// \param L Lua state -/// \param idx Lua table stack index which will be converted to a Lua string -/// \param schema_name Schema name to be used while converting the table -/// \param ctxidx Index (or pseudo-index) of clua context -const char *clua_check_schemed_json_string(lua_State *L, int idx, const std::string &schema_name, - int ctxidx = lua_upvalueindex(1)); - -/// \brief Parses a JSON from a string and pushes it as a Lua table (schemed version) -/// \param L Lua state -/// \param s JSON string -/// \param idx Lua table stack index which will be converted to a Lua string -/// \param schema_name Schema name to be used while converting the table -/// \param ctxidx Index (or pseudo-index) of clua context -void clua_push_schemed_json_table(lua_State *L, const char *s, const std::string &schema_name, - int ctxidx = lua_upvalueindex(1)); - -} // namespace cartesi - -#endif diff --git a/src/clua-machine.cpp b/src/clua-machine.cpp deleted file mode 100644 index 2b1b869b7..000000000 --- a/src/clua-machine.cpp +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT ANY -// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#include -#include -#include - -#include "clua-machine-util.h" -#include "clua.h" -#include "machine-c-api.h" - -namespace cartesi { - -/// \brief This is the machine.get_default_machine_config() -/// method implementation. -static int machine_class_index_get_default_config(lua_State *L) { - const char *config{}; - if (cm_get_default_config(&config) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - clua_push_json_table(L, config); - return 1; -} - -/// \brief This is the machine.get_reg_address() method implementation. -static int machine_class_index_get_reg_address(lua_State *L) { - uint64_t addr{}; - if (cm_get_reg_address(clua_check_cm_proc_reg(L, 1), &addr) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - lua_pushinteger(L, static_cast(addr)); - return 1; -} - -/// \brief This is the machine.verify_step_uarch() method implementation. -static int machine_class_index_verify_step_uarch(lua_State *L) { - lua_settop(L, 4); - const char *log = clua_check_schemed_json_string(L, 2, "AccessLog"); - cm_hash root_hash{}; - clua_check_cm_hash(L, 1, &root_hash); - cm_hash target_hash{}; - clua_check_cm_hash(L, 3, &target_hash); - if (cm_verify_step_uarch(&root_hash, log, &target_hash) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - return 0; -} - -/// \brief This is the machine.verify_reset_uarch() method implementation. -static int machine_class_index_verify_reset_uarch(lua_State *L) { - lua_settop(L, 4); - const char *log = clua_check_schemed_json_string(L, 2, "AccessLog"); - cm_hash root_hash{}; - clua_check_cm_hash(L, 1, &root_hash); - cm_hash target_hash{}; - clua_check_cm_hash(L, 3, &target_hash); - if (cm_verify_reset_uarch(&root_hash, log, &target_hash) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - return 0; -} - -/// \brief This is the machine.verify_send_cmio_response() method implementation. -static int machine_class_index_verify_send_cmio_response(lua_State *L) { - lua_settop(L, 6); - const uint16_t reason = static_cast(luaL_checkinteger(L, 1)); - size_t length{0}; - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - const auto *data = reinterpret_cast(luaL_checklstring(L, 2, &length)); - const char *log = clua_check_schemed_json_string(L, 4, "AccessLog"); - cm_hash root_hash{}; - clua_check_cm_hash(L, 3, &root_hash); - cm_hash target_hash{}; - clua_check_cm_hash(L, 5, &target_hash); - if (cm_verify_send_cmio_response(reason, data, length, &root_hash, log, &target_hash) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - return 0; -} - -/// \brief Contents of the machine class metatable __index table. -static const auto machine_class_index = cartesi::clua_make_luaL_Reg_array({ - {"get_default_config", machine_class_index_get_default_config}, - {"get_reg_address", machine_class_index_get_reg_address}, - {"verify_step_uarch", machine_class_index_verify_step_uarch}, - {"verify_reset_uarch", machine_class_index_verify_reset_uarch}, - {"verify_send_cmio_response", machine_class_index_verify_send_cmio_response}, -}); - -/// \brief This is the cartesi.machine() constructor implementation. -/// \param L Lua state. -static int machine_ctor(lua_State *L) { - lua_settop(L, 3); - auto &managed_machine = clua_push_to(L, clua_managed_cm_ptr(nullptr)); - const char *runtime_config = !lua_isnil(L, 3) ? clua_check_json_string(L, 3) : nullptr; - if (!lua_isstring(L, 2)) { - const char *config = clua_check_json_string(L, 2); - if (cm_create(config, runtime_config, &managed_machine.get()) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - } else { - const char *dir = luaL_checkstring(L, 2); - if (cm_load(dir, runtime_config, &managed_machine.get()) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - } - return 1; -} - -/// \brief Tag to identify the machine class-like constructor -struct machine_class {}; - -int clua_machine_init(lua_State *L, int ctxidx) { - clua_createnewtype>(L, ctxidx); - clua_createnewtype>(L, ctxidx); - clua_createnewtype>(L, ctxidx); - if (!clua_typeexists(L, ctxidx)) { - clua_createtype(L, "cartesi machine class", ctxidx); - clua_setmethods(L, machine_class_index.data(), 0, ctxidx); - static const auto machine_class_meta = cartesi::clua_make_luaL_Reg_array({ - {"__call", machine_ctor}, - }); - clua_setmetamethods(L, machine_class_meta.data(), 0, ctxidx); - } - return 1; -} - -int clua_machine_export(lua_State *L, int ctxidx) { - const int ctxabsidx = lua_absindex(L, ctxidx); - // cartesi - clua_machine_init(L, ctxabsidx); // cartesi - lua_newtable(L); // cartesi machine_class - clua_setmetatable(L, -1, ctxabsidx); // cartesi machine_class - lua_setfield(L, -2, "machine"); // cartesi - return 0; -} - -} // namespace cartesi diff --git a/src/clua.h b/src/clua.h index a6f74a22e..4ff32ec54 100644 --- a/src/clua.h +++ b/src/clua.h @@ -226,6 +226,7 @@ void clua_setmetatable(lua_State *L, int objidx, int ctxidx = lua_upvalueindex(1 /// template int clua_push(lua_State *L, T &&value, int ctxidx = lua_upvalueindex(1)) { + ctxidx = lua_absindex(L, ctxidx); T *ptr = static_cast(lua_newuserdata(L, sizeof(T))); new (ptr) T{std::forward(value)}; clua_setmetatable(L, -1, ctxidx); diff --git a/src/i-virtual-machine.h b/src/i-virtual-machine.h index 043cdfa74..75fc3f227 100644 --- a/src/i-virtual-machine.h +++ b/src/i-virtual-machine.h @@ -51,6 +51,26 @@ class i_virtual_machine { i_virtual_machine &operator=(const i_virtual_machine &other) = delete; i_virtual_machine &operator=(i_virtual_machine &&other) noexcept = delete; + /// \brief Clone an object of same underlying type but without a machine instance + i_virtual_machine *clone_empty(void) const { + return do_clone_empty(); + } + + /// \brief Tells if object is empty (does not holds a machine instance) + bool is_empty(void) const { + return do_is_empty(); + } + + /// \brief Create a machine from config + void create(const machine_config &config, const machine_runtime_config &runtime = {}) { + return do_create(config, runtime); + } + + /// \brief Load Create a machine from config + void load(const std::string &directory, const machine_runtime_config &runtime = {}) { + return do_load(directory, runtime); + } + /// \brief Runs the machine until mcycle reaches mcycle_end or the machine halts. interpreter_break_reason run(uint64_t mcycle_end) { return do_run(mcycle_end); @@ -136,24 +156,17 @@ class i_virtual_machine { return do_get_initial_config(); } - /// \brief destroy - void destroy(void) { - do_destroy(); + machine_runtime_config get_runtime_config(void) const { + return do_get_runtime_config(); } - /// \brief snapshot - void snapshot(void) { - do_snapshot(); + void set_runtime_config(const machine_runtime_config &r) { + return do_set_runtime_config(r); } - /// \brief commit - void commit(void) { - do_commit(); - } - - /// \brief rollback - void rollback(void) { - do_rollback(); + /// \brief destroy + void destroy(void) { + do_destroy(); } /// \brief Resets the microarchitecture state to pristine value @@ -184,13 +197,50 @@ class i_virtual_machine { do_send_cmio_response(reason, data, length); } - /// \brief Sends cmio response. and returns an access log + /// \brief Sends cmio response and returns an access log access_log log_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length, const access_log::type &log_type) { return do_log_send_cmio_response(reason, data, length, log_type); } + /// \brief Gets the address of any register + uint64_t get_reg_address(reg r) const { + return do_get_reg_address(r); + } + + /// \brief Returns copy of default machine config + machine_config get_default_config(void) const { + return do_get_default_config(); + } + + /// \brief Checks the validity of a state transition caused by log_step_uarch. + void verify_step_uarch(const hash_type &root_hash_before, const access_log &log, + const hash_type &root_hash_after) const { + return do_verify_step_uarch(root_hash_before, log, root_hash_after); + } + + /// \brief Checks the validity of a state transition caused by log_reset_uarch. + void verify_reset_uarch(const hash_type &root_hash_before, const access_log &log, + const hash_type &root_hash_after) const { + return do_verify_reset_uarch(root_hash_before, log, root_hash_after); + } + + /// \brief Checks the validity of state transitions caused by log_send_cmio_response. + void verify_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length, + const hash_type &root_hash_before, const access_log &log, const hash_type &root_hash_after) const { + return do_verify_send_cmio_response(reason, data, length, root_hash_before, log, root_hash_after); + } + + /// \brief Checks if implementation is jsorpc-virtual-machine + bool is_jsonrpc_virtual_machine(void) const { + return do_is_jsonrpc_virtual_machine(); + } + private: + virtual i_virtual_machine *do_clone_empty(void) const = 0; + virtual bool do_is_empty(void) const = 0; + virtual void do_create(const machine_config &config, const machine_runtime_config &runtime) = 0; + virtual void do_load(const std::string &directory, const machine_runtime_config &runtime) = 0; virtual interpreter_break_reason do_run(uint64_t mcycle_end) = 0; virtual void do_store(const std::string &dir) const = 0; virtual access_log do_log_step_uarch(const access_log::type &log_type) = 0; @@ -208,10 +258,9 @@ class i_virtual_machine { virtual uint64_t do_read_word(uint64_t address) const = 0; virtual bool do_verify_dirty_page_maps(void) const = 0; virtual machine_config do_get_initial_config(void) const = 0; - virtual void do_snapshot() = 0; + virtual machine_runtime_config do_get_runtime_config(void) const = 0; + virtual void do_set_runtime_config(const machine_runtime_config &r) = 0; virtual void do_destroy() = 0; - virtual void do_commit() = 0; - virtual void do_rollback() = 0; virtual void do_reset_uarch() = 0; virtual access_log do_log_reset_uarch(const access_log::type &log_type) = 0; virtual uarch_interpreter_break_reason do_run_uarch(uint64_t uarch_cycle_end) = 0; @@ -219,6 +268,17 @@ class i_virtual_machine { virtual void do_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length) = 0; virtual access_log do_log_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length, const access_log::type &log_type) = 0; + virtual uint64_t do_get_reg_address(reg r) const = 0; + virtual machine_config do_get_default_config(void) const = 0; + virtual void do_verify_step_uarch(const hash_type &root_hash_before, const access_log &log, + const hash_type &root_hash_after) const = 0; + virtual void do_verify_reset_uarch(const hash_type &root_hash_before, const access_log &log, + const hash_type &root_hash_after) const = 0; + virtual void do_verify_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length, + const hash_type &root_hash_before, const access_log &log, const hash_type &root_hash_after) const = 0; + virtual bool do_is_jsonrpc_virtual_machine(void) const { + return false; + } }; } // namespace cartesi diff --git a/src/json-util.h b/src/json-util.h index f47e3f5e4..5680cdf9e 100644 --- a/src/json-util.h +++ b/src/json-util.h @@ -25,8 +25,7 @@ #define JSON_HAS_FILESYSTEM 0 // NOLINT(cppcoreguidelines-macro-usage) #include -#include "jsonrpc-connection.h" -#include "jsonrpc-virtual-machine.h" +#include "jsonrpc-fork-result.h" #include "machine-merkle-tree.h" #include "machine.h" #include "semantic-version.h" diff --git a/src/jsonrpc-connection.h b/src/jsonrpc-connection.h deleted file mode 100644 index e1ed5d292..000000000 --- a/src/jsonrpc-connection.h +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT ANY -// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along -// with this program (see COPYING). If not, see . -// - -#ifndef JSONRPC_CONNECTION_H -#define JSONRPC_CONNECTION_H - -#include - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#include "asio-config.h" // must be included before any ASIO header -#include -#include -#include -#pragma GCC diagnostic pop - -#include "semantic-version.h" - -namespace cartesi { - -/// Result of a fork -struct fork_result final { - std::string address{}; - uint32_t pid{}; -}; - -class jsonrpc_connection final { -public: - jsonrpc_connection(std::string remote_address, bool detach_server); - jsonrpc_connection(const jsonrpc_connection &other) = delete; - jsonrpc_connection(jsonrpc_connection &&other) noexcept = delete; - jsonrpc_connection &operator=(const jsonrpc_connection &other) = delete; - jsonrpc_connection &operator=(jsonrpc_connection &&other) noexcept = delete; - ~jsonrpc_connection(); - bool is_snapshot(void) const; - bool is_shutdown(void) const; - boost::beast::tcp_stream &get_stream(void); - const boost::beast::tcp_stream &get_stream(void) const; - const std::string &get_remote_address(void) const; - const std::string &get_remote_parent_address(void) const; - void snapshot(void); - void commit(void); - void rollback(void); - - void shutdown_server(void); - fork_result fork_server(void); - std::string rebind_server(const std::string &address); - semantic_version get_server_version(void); - -private: - boost::asio::io_context m_ioc{1}; // The io_context is required for all I/O - boost::beast::tcp_stream m_stream{m_ioc}; // TCP stream for keep alive connections - boost::container::static_vector m_address{}; - const bool m_detach_server; -}; - -} // namespace cartesi - -#endif diff --git a/src/jsonrpc-discover.json b/src/jsonrpc-discover.json index a28928611..1b9fea631 100644 --- a/src/jsonrpc-discover.json +++ b/src/jsonrpc-discover.json @@ -1,43 +1,31 @@ { "openrpc": "1.0.0-rc1", "info": { - "version": "0.0.2", "title": "Remote Cartesi Machine", + "version": "0.5.0", + "description": "API for controling a remote Cartesi Machine server", "license": { "name": "MIT" } }, "methods": [ - - { - "name": "rpc.discover", - "description": "Returns an OpenRPC schema as a description of this service", - "params": [], - "result": { - "name": "OpenRPC Schema", - "schema": { - "$ref": "https://raw.githubusercontent.com/open-rpc/meta-schema/master/schema.json" - } - } - }, - { "name": "fork", "summary": "Forks the server", "params": [], "result": { "name": "fork_result", - "description": "Result of the fork containing address and pid", + "description": "Information about the forked child", "schema": { "$ref": "#/components/schemas/ForkResult" } } }, - { "name": "rebind", "summary": "Changes the address the server is listening to", - "params": [ { + "params": [ + { "name": "address", "description": "URL of the new address", "required": true, @@ -48,13 +36,12 @@ ], "result": { "name": "address", - "description": "URL of the rebound address", + "description": "URL of address server actually bound to", "schema": { "type": "string" } } }, - { "name": "shutdown", "summary": "Causes the server to shutdown and exit", @@ -67,7 +54,6 @@ } } }, - { "name": "get_version", "summary": "Returns the server version", @@ -80,19 +66,65 @@ } } }, - { - "name": "machine.machine.config", + "name": "is_empty", + "summary": "Checks if server has no machine instance", + "params": [], + "result": { + "name": "empty", + "description": "True if server has no machine", + "schema": { + "type": "boolean" + } + } + }, + { + "name": "machine.set_runtime_config", + "summary": "Changes the machine runtime configuration", + "params": [ + { + "name": "runtime_config", + "description": "Machine runtime configuration", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineRuntimeConfig" + } + } + ], + "result": { + "name": "status", + "description": "True when operation succeeded", + "schema": { + "type": "boolean" + } + } + }, + { + "name": "machine.get_runtime_config", + "summary": "Returns the current machine runtime configuration", + "params": [], + "result": { + "name": "runtime_config", + "description": "Machine runtime configuration", + "schema": { + "$ref": "#/components/schemas/MachineRuntimeConfig" + } + } + }, + { + "name": "machine.create", "summary": "Instantiates a machine from a machine configuration", - "params": [ { - "name":"config", + "params": [ + { + "name": "config", "description": "Machine configuration", "required": true, "schema": { "$ref": "#/components/schemas/MachineConfig" } - }, { - "name":"runtime", + }, + { + "name": "runtime_config", "description": "Machine runtime configuration", "required": false, "schema": { @@ -108,19 +140,20 @@ } } }, - { - "name": "machine.machine.directory", + "name": "machine.load", "summary": "Instantiates a machine from a directory where a machine instance was stored", - "params": [ { - "name":"directory", + "params": [ + { + "name": "directory", "description": "Directory with stored machine instance", "required": true, "schema": { "type": "string" } - }, { - "name":"runtime", + }, + { + "name": "runtime_config", "description": "Machine runtime configuration", "required": false, "schema": { @@ -136,11 +169,10 @@ } } }, - { "name": "machine.destroy", "summary": "Destroys current machine instance", - "params": [ ], + "params": [], "result": { "name": "status", "description": "True when operation succeeded", @@ -149,12 +181,12 @@ } } }, - { "name": "machine.store", "summary": "Stores machine instance in a directory", - "params": [ { - "name":"directory", + "params": [ + { + "name": "directory", "description": "Directory to stored machine instance", "required": true, "schema": { @@ -170,12 +202,12 @@ } } }, - { "name": "machine.run", "summary": "Runs the emulator until a given cycle", - "params": [ { - "name":"mcycle_end", + "params": [ + { + "name": "mcycle_end", "description": "The maximum value of the cycle counter", "required": true, "schema": { @@ -191,12 +223,12 @@ } } }, - { "name": "machine.run_uarch", "summary": "Runs the small emulator until a given cycle", - "params": [ { - "name":"uarch_cycle_end", + "params": [ + { + "name": "uarch_cycle_end", "description": "The maximum value of the cycle counter", "required": true, "schema": { @@ -212,12 +244,12 @@ } } }, - { "name": "machine.log_step_uarch", "summary": "Runs the small emulator for one cycle and return a log of state accesses", - "params": [ { - "name":"log_type", + "params": [ + { + "name": "log_type", "description": "The maximum value of the cycle counter", "required": true, "schema": { @@ -233,26 +265,28 @@ } } }, - { "name": "machine.verify_step_uarch", "summary": "Verifies a state transition caused by log_step_uarch", - "params": [ { - "name":"root_hash_before", + "params": [ + { + "name": "root_hash_before", "description": "State hash before transition described by access log", "required": true, "schema": { "$ref": "#/components/schemas/Base64Hash" } - }, { - "name":"log", + }, + { + "name": "log", "description": "Access log describing transition", "required": true, "schema": { "$ref": "#/components/schemas/AccessLog" } - }, { - "name":"root_hash_after", + }, + { + "name": "root_hash_after", "description": "State hash after transition described by access log", "required": true, "schema": { @@ -268,26 +302,28 @@ } } }, - { "name": "machine.verify_reset_uarch", "summary": "Verifies a state transition caused by log_reset_uarch", - "params": [ { - "name":"root_hash_before", + "params": [ + { + "name": "root_hash_before", "description": "State hash before transition described by access log", "required": true, "schema": { "$ref": "#/components/schemas/Base64Hash" } - }, { - "name":"log", + }, + { + "name": "log", "description": "Access log describing transition", "required": true, "schema": { "$ref": "#/components/schemas/AccessLog" } - }, { - "name":"root_hash_after", + }, + { + "name": "root_hash_after", "description": "State hash after transition described by access log", "required": true, "schema": { @@ -303,11 +339,10 @@ } } }, - { "name": "machine.get_root_hash", "summary": "Obtains the Merkle hash of the current machine state", - "params": [ ], + "params": [], "result": { "name": "hash", "description": "Merkle hash", @@ -316,19 +351,20 @@ } } }, - { "name": "machine.get_proof", "summary": "Obtains a Merkle proof for a range in the machine state", - "params": [ { - "name":"address", + "params": [ + { + "name": "address", "description": "Starting address of range in state (must be aligned to size)", "required": true, "schema": { "$ref": "#/components/schemas/UnsignedInteger" } - }, { - "name":"log2_size", + }, + { + "name": "log2_size", "description": "Log2 of size of range", "required": true, "schema": { @@ -344,12 +380,12 @@ } } }, - { "name": "machine.read_word", "summary": "Reads a 64-bit word from memory (must be aligned)", - "params": [ { - "name":"address", + "params": [ + { + "name": "address", "description": "Starting physical address of word", "required": true, "schema": { @@ -365,19 +401,20 @@ } } }, - { "name": "machine.read_memory", "summary": "Reads a span of memory from the state (must be contained in the same memory range)", - "params": [ { - "name":"address", + "params": [ + { + "name": "address", "description": "Starting physical address of span", "required": true, "schema": { "$ref": "#/components/schemas/UnsignedInteger" } - } , { - "name":"length", + }, + { + "name": "length", "description": "Length of span", "required": true, "schema": { @@ -393,19 +430,20 @@ } } }, - { "name": "machine.write_memory", "summary": "Writes a span of memory to the state (must be contained in the same memory range)", - "params": [ { - "name":"address", + "params": [ + { + "name": "address", "description": "Starting physical address of span", "required": true, "schema": { "$ref": "#/components/schemas/UnsignedInteger" } - } , { - "name":"data", + }, + { + "name": "data", "description": "Span of memory", "required": true, "schema": { @@ -421,19 +459,20 @@ } } }, - { "name": "machine.read_virtual_memory", "summary": "Reads a span of memory from the state (must be contained in the same memory range)", - "params": [ { - "name":"address", + "params": [ + { + "name": "address", "description": "Starting virtual address of span according to current mapping", "required": true, "schema": { "$ref": "#/components/schemas/UnsignedInteger" } - } , { - "name":"length", + }, + { + "name": "length", "description": "Length of span", "required": true, "schema": { @@ -449,19 +488,20 @@ } } }, - { "name": "machine.write_virtual_memory", "summary": "Writes a span of memory to the state (must be contained in the same memory range)", - "params": [ { - "name":"address", + "params": [ + { + "name": "address", "description": "Starting virtual address of span according to current mapping", "required": true, "schema": { "$ref": "#/components/schemas/UnsignedInteger" } - } , { - "name":"data", + }, + { + "name": "data", "description": "Span of memory", "required": true, "schema": { @@ -477,12 +517,12 @@ } } }, - { "name": "machine.translate_virtual_address", "summary": "Translates a virtual memory address to its corresponding physical memory address", - "params": [ { - "name":"vaddr", + "params": [ + { + "name": "vaddr", "description": "Virtual address to translate", "required": true, "schema": { @@ -498,12 +538,12 @@ } } }, - { "name": "machine.replace_memory_range", "summary": "Replaces a memory range", - "params": [ { - "name":"range", + "params": [ + { + "name": "range", "description": "New memory range (must be compatible with existing range)", "required": true, "schema": { @@ -519,12 +559,12 @@ } } }, - { "name": "machine.read_register", "summary": "Reads the value of a register", - "params": [ { - "name":"reg", + "params": [ + { + "name": "reg", "description": "Register to read", "required": true, "schema": { @@ -540,19 +580,20 @@ } } }, - { "name": "machine.write_reg", "summary": "Writes the value of a register", - "params": [ { - "name":"reg", + "params": [ + { + "name": "reg", "description": "Register to write", "required": true, "schema": { "$ref": "#/components/schemas/REG" } - }, { - "name":"value", + }, + { + "name": "value", "description": "Value of register (little-endian)", "required": true, "schema": { @@ -568,12 +609,12 @@ } } }, - { "name": "machine.get_reg_address", "summary": "Returns the address of a register", - "params": [ { - "name":"reg", + "params": [ + { + "name": "reg", "description": "Register to obtain address", "required": true, "schema": { @@ -589,7 +630,6 @@ } } }, - { "name": "machine.reset_uarch", "summary": "Reset uarch to pristine state", @@ -602,12 +642,12 @@ } } }, - { "name": "machine.log_reset_uarch", "summary": "Reset uarch to pristine state and return a log of state accesses", - "params": [ { - "name":"log_type", + "params": [ + { + "name": "log_type", "description": "The maximum value of the cycle counter", "required": true, "schema": { @@ -623,7 +663,6 @@ } } }, - { "name": "machine.get_initial_config", "summary": "Returns initial machine configuration for instance", @@ -636,7 +675,6 @@ } } }, - { "name": "machine.get_default_config", "summary": "Returns default machine configuration", @@ -649,7 +687,6 @@ } } }, - { "name": "machine.verify_merkle_tree", "summary": "Verifies sanity of Merkle tree", @@ -662,7 +699,6 @@ } } }, - { "name": "machine.verify_dirty_page_maps", "summary": "Verifies sanity of dirty page maps", @@ -675,7 +711,6 @@ } } }, - { "name": "machine.get_memory_ranges", "summary": "Returns a list with descriptions for all of the machine's memory ranges", @@ -688,19 +723,20 @@ } } }, - { "name": "machine.send_cmio_response", "summary": "Sends cmio response.", - "params": [ { - "name":"reason", + "params": [ + { + "name": "reason", "description": "Reason for sending response", "required": true, "schema": { "$ref": "#/components/schemas/UnsignedInteger" } - },{ - "name":"data", + }, + { + "name": "data", "description": "Response data to send", "required": true, "schema": { @@ -716,26 +752,28 @@ } } }, - { "name": "machine.log_send_cmio_response", "summary": "Sends cmio response and returns an access log", - "params": [ { - "name":"reason", - "description": "Reason for sending response", - "required": true, - "schema": { - "$ref": "#/components/schemas/UnsignedInteger" - } - }, { - "name":"data", + "params": [ + { + "name": "reason", + "description": "Reason for sending response", + "required": true, + "schema": { + "$ref": "#/components/schemas/UnsignedInteger" + } + }, + { + "name": "data", "description": "Response data to send", "required": true, "schema": { "$ref": "#/components/schemas/Base64String" } - },{ - "name":"log_type", + }, + { + "name": "log_type", "description": "The log type to generate", "required": true, "schema": { @@ -751,40 +789,44 @@ } } }, - { "name": "machine.verify_send_cmio_response", "summary": "Verifies a state transition caused by log_send_cmio_response", - "params": [ { - "name":"reason", - "description": "Reason for sending response", - "required": true, - "schema": { - "$ref": "#/components/schemas/UnsignedInteger" - } - }, { - "name":"data", + "params": [ + { + "name": "reason", + "description": "Reason for sending response", + "required": true, + "schema": { + "$ref": "#/components/schemas/UnsignedInteger" + } + }, + { + "name": "data", "description": "The response data sent when the log was generated.", "required": true, "schema": { "$ref": "#/components/schemas/Base64String" } - }, { - "name":"root_hash_before", + }, + { + "name": "root_hash_before", "description": "State hash before response was sent.", "required": true, "schema": { "$ref": "#/components/schemas/Base64Hash" } - }, { - "name":"log", + }, + { + "name": "log", "description": "State access log to be verified.", "required": true, "schema": { "$ref": "#/components/schemas/AccessLog" } - }, { - "name":"root_hash_after", + }, + { + "name": "root_hash_after", "description": "State hash after response was sent.", "required": true, "schema": { @@ -800,188 +842,26 @@ } } } - ], - "components": { "schemas": { - - "REG": { - "title": "REG", - "enum": [ - "x0", - "x1", - "x2", - "x3", - "x4", - "x5", - "x6", - "x7", - "x8", - "x9", - "x10", - "x11", - "x12", - "x13", - "x14", - "x15", - "x16", - "x17", - "x18", - "x19", - "x20", - "x21", - "x22", - "x23", - "x24", - "x25", - "x26", - "x27", - "x28", - "x29", - "x30", - "x31", - "f0", - "f1", - "f2", - "f3", - "f4", - "f5", - "f6", - "f7", - "f8", - "f9", - "f10", - "f11", - "f12", - "f13", - "f14", - "f15", - "f16", - "f17", - "f18", - "f19", - "f20", - "f21", - "f22", - "f23", - "f24", - "f25", - "f26", - "f27", - "f28", - "f29", - "f30", - "f31", - "pc", - "fcsr", - "mvendorid", - "marchid", - "mimpid", - "mcycle", - "icycleinstret", - "mstatus", - "mtvec", - "mscratch", - "mepc", - "mcause", - "mtval", - "misa", - "mie", - "mip", - "medeleg", - "mideleg", - "mcounteren", - "menvcfg", - "stvec", - "sscratch", - "sepc", - "scause", - "stval", - "satp", - "scounteren", - "senvcfg", - "ilrsc", - "iflags", - "iunrep", - "clint_mtimecmp", - "plic_girqpend", - "plic_girqsrvd", - "htif_tohost", - "htif_fromhost", - "htif_ihalt", - "htif_iconsole", - "htif_iyield", - "uarch_x0", - "uarch_x1", - "uarch_x2", - "uarch_x3", - "uarch_x4", - "uarch_x5", - "uarch_x6", - "uarch_x7", - "uarch_x8", - "uarch_x9", - "uarch_x10", - "uarch_x11", - "uarch_x12", - "uarch_x13", - "uarch_x14", - "uarch_x15", - "uarch_x16", - "uarch_x17", - "uarch_x18", - "uarch_x19", - "uarch_x20", - "uarch_x21", - "uarch_x22", - "uarch_x23", - "uarch_x24", - "uarch_x25", - "uarch_x26", - "uarch_x27", - "uarch_x28", - "uarch_x29", - "uarch_x30", - "uarch_x31", - "uarch_pc", - "uarch_cycle", - "uarch_halt_flag", - "iflags_prv", - "iflags_x", - "iflags_y", - "iflags_h", - "htif_tohost_dev", - "htif_tohost_cmd", - "htif_tohost_reason", - "htif_tohost_data", - "htif_fromhost_dev", - "htif_fromhost_cmd", - "htif_fromhost_reason", - "htif_fromhost_data" - ] - }, - - "InterpreterBreakReason": { - "title": "InterpreterBreakReason", - "enum": [ - "failed", - "halted", - "yielded_manually", - "yielded_automatically", - "yielded_softly", - "reached_target_mcycle" - ] + "UnsignedInteger": { + "title": "UnsignedInteger", + "type": "integer", + "minimum": 0 }, - - "UarchInterpreterBreakReason": { - "title": "UarchInterpreterBreakReason", - "enum": [ - "reached_target_cycle", - "uarch_halted" - ] + "ForkResult": { + "title": "ForkResult", + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "pid": { + "$ref": "#/components/schemas/UnsignedInteger" + } + } }, - "SemanticVersion": { "title": "SemanticVersion", "type": "object", @@ -1008,7 +888,48 @@ } } }, - + "ConcurrencyRuntimeConfig": { + "title": "ConcurrencyRuntimeConfig", + "type": "object", + "properties": { + "update_merkle_tree": { + "$ref": "#/components/schemas/UnsignedInteger" + } + } + }, + "HTIFRuntimeConfig": { + "title": "HTIFRuntimeConfig", + "type": "object", + "properties": { + "no_console_putchar": { + "type": "boolean" + } + } + }, + "MachineRuntimeConfig": { + "title": "MachineRuntimeConfig", + "type": "object", + "properties": { + "concurrency": { + "$ref": "#/components/schemas/ConcurrencyRuntimeConfig" + }, + "htif": { + "$ref": "#/components/schemas/HTIFRuntimeConfig" + }, + "skip_root_hash_check": { + "type": "boolean" + }, + "skip_root_hash_store": { + "type": "boolean" + }, + "skip_version_check": { + "type": "boolean" + }, + "soft_yield": { + "type": "boolean" + } + } + }, "ProcessorConfig": { "title": "ProcessorConfig", "type": "object", @@ -1300,206 +1221,6 @@ } } }, - - "UnsignedInteger": { - "title": "UnsignedInteger", - "type": "integer", - "minimum": 0 - }, - - "Base64String": { - "title": "Base64String", - "type": "string", - "contentEncoding": "base64" - }, - - "Base64Hash": { - "title": "Base64Hash", - "type": "string", - "contentEncoding": "base64", - "minLength": 45, - "maxLength": 45 - }, - - "Base64HashArray": { - "title": "Base64HashArray", - "type": "array", - "items": { - "$ref": "#/components/schemas/Base64Hash" - } - }, - - "NoteArray": { - "title": "NoteArray", - "type": "array", - "items": { - "type": "string" - } - }, - - "BracketArray": { - "title": "BracketArray", - "type": "array", - "items": { - "$ref": "#/components/schemas/Bracket" - } - }, - - "BracketType": { - "title": "BracketType", - "enum": [ - "begin", - "end" - ] - }, - - "Bracket": { - "title": "Bracket", - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/BracketType" - }, - "where": { - "$ref": "#/components/schemas/UnsignedInteger" - }, - "text": { - "type": "string" - } - }, - "required": [ - "type", - "where", - "text" - ] - }, - - "Proof": { - "title": "Proof", - "type": "object", - "properties": { - "target_address": { - "$ref": "#/components/schemas/UnsignedInteger" - }, - "log2_target_size": { - "$ref": "#/components/schemas/UnsignedInteger" - }, - "target_hash": { - "$ref": "#/components/schemas/Base64Hash" - }, - "log2_root_size": { - "$ref": "#/components/schemas/UnsignedInteger" - }, - "root_hash": { - "$ref": "#/components/schemas/Base64Hash" - }, - "sibling_hashes": { - "$ref": "#/components/schemas/Base64HashArray" - } - }, - "required": [ - "target_address", - "log2_target_size", - "target_hash", - "log2_root_size", - "root_hash", - "sibling_hashes" - ] - }, - - "Access": { - "title": "Access", - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/AccessType" - }, - "address": { - "$ref": "#/components/schemas/UnsignedInteger" - }, - "log2_size": { - "$ref": "#/components/schemas/UnsignedInteger" - }, - "read_hash": { - "$ref": "#/components/schemas/Base64String" - }, - "read": { - "$ref": "#/components/schemas/Base64String" - }, - "written_hash": { - "$ref": "#/components/schemas/Base64String" - }, - "written": { - "$ref": "#/components/schemas/Base64String" - }, - "sibling_hashes": { - "$ref": "#/components/schemas/Base64HashArray" - } - }, - "required": [ - "type", - "address", - "log2_size", - "read_hash" - ] - }, - - "AccessArray": { - "title": "AccessArray", - "type": "array", - "items": { - "$ref": "#/components/schemas/Access" - } - }, - - "AccessType": { - "title": "AccessType", - "enum": [ - "read", - "write" - ] - }, - - "AccessLogType": { - "title": "AccessLogType", - "type": "object", - "properties": { - "has_annotations": { - "type": "boolean" - }, - "has_large_data": { - "type": "boolean" - } - }, - "required": [ - "has_annotations", - "has_large_data" - ] - }, - - "AccessLog": { - "title": "AccessLog", - "type": "object", - "properties": { - "log_type": { - "$ref": "#/components/schemas/AccessLogType" - }, - "accesses": { - "$ref": "#/components/schemas/AccessArray" - }, - "notes": { - "$ref": "#/components/schemas/NoteArray" - }, - "brackets": { - "$ref": "#/components/schemas/BracketArray" - } - }, - "required": [ - "log_type", - "accesses" - ] - }, - "RAMConfig": { "title": "RAMConfig", "type": "object", @@ -1515,7 +1236,6 @@ "length" ] }, - "DTBConfig": { "title": "DTBConfig", "type": "object", @@ -1534,7 +1254,6 @@ } } }, - "MemoryRangeConfig": { "title": "MemoryRangeConfig", "type": "object", @@ -1553,7 +1272,6 @@ } } }, - "CmioBufferConfig": { "title": "CmioBufferConfig", "type": "object", @@ -1566,7 +1284,6 @@ } } }, - "VirtIOHostfwd": { "title": "VirtIOHostfwd", "type": "object", @@ -1588,7 +1305,6 @@ } } }, - "VirtIOHostfwdArray": { "title": "VirtIOHostfwdArray", "type": "array", @@ -1596,7 +1312,6 @@ "$ref": "#/components/schemas/VirtIOHostfwd" } }, - "VirtIODeviceConfig": { "title": "VirtIODeviceConfig", "type": "object", @@ -1621,7 +1336,6 @@ } } }, - "FlashDriveConfigs": { "title": "FlashDriveConfigs", "type": "array", @@ -1629,7 +1343,6 @@ "$ref": "#/components/schemas/MemoryRangeConfig" } }, - "TLBConfig": { "title": "TLBConfig", "type": "object", @@ -1639,7 +1352,6 @@ } } }, - "CLINTConfig": { "title": "CLINTConfig", "type": "object", @@ -1649,7 +1361,6 @@ } } }, - "PLICConfig": { "title": "PLICConfig", "type": "object", @@ -1662,7 +1373,6 @@ } } }, - "HTIFConfig": { "title": "HTIFConfig", "type": "object", @@ -1684,7 +1394,6 @@ } } }, - "UarchProcessorConfig": { "title": "UarchProcessorConfig", "type": "object", @@ -1796,7 +1505,6 @@ } } }, - "UarchRAMConfig": { "title": "UarchRAMConfig", "type": "object", @@ -1809,7 +1517,6 @@ } } }, - "UarchConfig": { "title": "UarchConfig", "type": "object", @@ -1822,7 +1529,6 @@ } } }, - "CmioConfig": { "title": "CmioConfig", "type": "object", @@ -1835,7 +1541,6 @@ } } }, - "VirtIOConfigs": { "title": "VirtIOConfigs", "type": "array", @@ -1843,7 +1548,6 @@ "$ref": "#/components/schemas/VirtIODeviceConfig" } }, - "MachineConfig": { "title": "MachineConfig", "type": "object", @@ -1883,52 +1587,359 @@ } } }, - - "ConcurrencyRuntimeConfig": { - "title": "ConcurrencyRuntimeConfig", - "type": "object", - "properties": { - "update_merkle_tree": { - "$ref": "#/components/schemas/UnsignedInteger" - } - } + "InterpreterBreakReason": { + "title": "InterpreterBreakReason", + "enum": [ + "failed", + "halted", + "yielded_manually", + "yielded_automatically", + "yielded_softly", + "reached_target_mcycle" + ] }, - - "HTIFRuntimeConfig": { - "title": "HTIFRuntimeConfig", + "UarchInterpreterBreakReason": { + "title": "UarchInterpreterBreakReason", + "enum": [ + "reached_target_cycle", + "uarch_halted" + ] + }, + "Base64String": { + "title": "Base64String", + "type": "string", + "contentEncoding": "base64" + }, + "Base64Hash": { + "title": "Base64Hash", + "type": "string", + "contentEncoding": "base64", + "minLength": 45, + "maxLength": 45 + }, + "Base64HashArray": { + "title": "Base64HashArray", + "type": "array", + "items": { + "$ref": "#/components/schemas/Base64Hash" + } + }, + "NoteArray": { + "title": "NoteArray", + "type": "array", + "items": { + "type": "string" + } + }, + "BracketArray": { + "title": "BracketArray", + "type": "array", + "items": { + "$ref": "#/components/schemas/Bracket" + } + }, + "BracketType": { + "title": "BracketType", + "enum": [ + "begin", + "end" + ] + }, + "Bracket": { + "title": "Bracket", "type": "object", "properties": { - "no_console_putchar": { - "type": "boolean" + "type": { + "$ref": "#/components/schemas/BracketType" + }, + "where": { + "$ref": "#/components/schemas/UnsignedInteger" + }, + "text": { + "type": "string" } - } + }, + "required": [ + "type", + "where", + "text" + ] }, - - "MachineRuntimeConfig": { - "title": "MachineRuntimeConfig", + "Proof": { + "title": "Proof", "type": "object", "properties": { - "concurrency": { - "$ref": "#/components/schemas/ConcurrencyRuntimeConfig" + "target_address": { + "$ref": "#/components/schemas/UnsignedInteger" }, - "htif": { - "$ref": "#/components/schemas/HTIFRuntimeConfig" + "log2_target_size": { + "$ref": "#/components/schemas/UnsignedInteger" }, - "skip_root_hash_check": { - "type": "boolean" + "target_hash": { + "$ref": "#/components/schemas/Base64Hash" }, - "skip_root_hash_store": { - "type": "boolean" + "log2_root_size": { + "$ref": "#/components/schemas/UnsignedInteger" }, - "skip_version_check": { + "root_hash": { + "$ref": "#/components/schemas/Base64Hash" + }, + "sibling_hashes": { + "$ref": "#/components/schemas/Base64HashArray" + } + }, + "required": [ + "target_address", + "log2_target_size", + "target_hash", + "log2_root_size", + "root_hash", + "sibling_hashes" + ] + }, + "Access": { + "title": "Access", + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/AccessType" + }, + "address": { + "$ref": "#/components/schemas/UnsignedInteger" + }, + "log2_size": { + "$ref": "#/components/schemas/UnsignedInteger" + }, + "read_hash": { + "$ref": "#/components/schemas/Base64String" + }, + "read": { + "$ref": "#/components/schemas/Base64String" + }, + "written_hash": { + "$ref": "#/components/schemas/Base64String" + }, + "written": { + "$ref": "#/components/schemas/Base64String" + }, + "sibling_hashes": { + "$ref": "#/components/schemas/Base64HashArray" + } + }, + "required": [ + "type", + "address", + "log2_size", + "read_hash" + ] + }, + "AccessArray": { + "title": "AccessArray", + "type": "array", + "items": { + "$ref": "#/components/schemas/Access" + } + }, + "AccessType": { + "title": "AccessType", + "enum": [ + "read", + "write" + ] + }, + "AccessLogType": { + "title": "AccessLogType", + "type": "object", + "properties": { + "has_annotations": { "type": "boolean" }, - "soft_yield": { + "has_large_data": { "type": "boolean" } - } + }, + "required": [ + "has_annotations", + "has_large_data" + ] + }, + "AccessLog": { + "title": "AccessLog", + "type": "object", + "properties": { + "log_type": { + "$ref": "#/components/schemas/AccessLogType" + }, + "accesses": { + "$ref": "#/components/schemas/AccessArray" + }, + "notes": { + "$ref": "#/components/schemas/NoteArray" + }, + "brackets": { + "$ref": "#/components/schemas/BracketArray" + } + }, + "required": [ + "log_type", + "accesses" + ] + }, + "REG": { + "title": "REG", + "enum": [ + "x0", + "x1", + "x2", + "x3", + "x4", + "x5", + "x6", + "x7", + "x8", + "x9", + "x10", + "x11", + "x12", + "x13", + "x14", + "x15", + "x16", + "x17", + "x18", + "x19", + "x20", + "x21", + "x22", + "x23", + "x24", + "x25", + "x26", + "x27", + "x28", + "x29", + "x30", + "x31", + "f0", + "f1", + "f2", + "f3", + "f4", + "f5", + "f6", + "f7", + "f8", + "f9", + "f10", + "f11", + "f12", + "f13", + "f14", + "f15", + "f16", + "f17", + "f18", + "f19", + "f20", + "f21", + "f22", + "f23", + "f24", + "f25", + "f26", + "f27", + "f28", + "f29", + "f30", + "f31", + "pc", + "fcsr", + "mvendorid", + "marchid", + "mimpid", + "mcycle", + "icycleinstret", + "mstatus", + "mtvec", + "mscratch", + "mepc", + "mcause", + "mtval", + "misa", + "mie", + "mip", + "medeleg", + "mideleg", + "mcounteren", + "menvcfg", + "stvec", + "sscratch", + "sepc", + "scause", + "stval", + "satp", + "scounteren", + "senvcfg", + "ilrsc", + "iflags", + "iunrep", + "clint_mtimecmp", + "plic_girqpend", + "plic_girqsrvd", + "htif_tohost", + "htif_fromhost", + "htif_ihalt", + "htif_iconsole", + "htif_iyield", + "uarch_x0", + "uarch_x1", + "uarch_x2", + "uarch_x3", + "uarch_x4", + "uarch_x5", + "uarch_x6", + "uarch_x7", + "uarch_x8", + "uarch_x9", + "uarch_x10", + "uarch_x11", + "uarch_x12", + "uarch_x13", + "uarch_x14", + "uarch_x15", + "uarch_x16", + "uarch_x17", + "uarch_x18", + "uarch_x19", + "uarch_x20", + "uarch_x21", + "uarch_x22", + "uarch_x23", + "uarch_x24", + "uarch_x25", + "uarch_x26", + "uarch_x27", + "uarch_x28", + "uarch_x29", + "uarch_x30", + "uarch_x31", + "uarch_pc", + "uarch_cycle", + "uarch_halt_flag", + "iflags_prv", + "iflags_x", + "iflags_y", + "iflags_h", + "htif_tohost_dev", + "htif_tohost_cmd", + "htif_tohost_reason", + "htif_tohost_data", + "htif_fromhost_dev", + "htif_fromhost_cmd", + "htif_fromhost_reason", + "htif_fromhost_data" + ] }, - "MemoryRangeDescription": { "title": "MemoryRangeDescription", "type": "object", @@ -1944,26 +1955,12 @@ } } }, - "MemoryRangeDescriptionArray": { "title": "MemoryRangeDescriptionArray", "type": "array", "items": { "$ref": "#/components/schemas/MemoryRangeDescription" } - }, - - "ForkResult": { - "title": "ForkResult", - "type": "object", - "properties": { - "address": { - "type": "string" - }, - "pid": { - "$ref": "#/components/schemas/UnsignedInteger" - } - } } } } diff --git a/src/clua-machine.h b/src/jsonrpc-fork-result.h similarity index 63% rename from src/clua-machine.h rename to src/jsonrpc-fork-result.h index 2b08af9b7..fc5a66b05 100644 --- a/src/clua-machine.h +++ b/src/jsonrpc-fork-result.h @@ -14,28 +14,16 @@ // with this program (see COPYING). If not, see . // -#ifndef CLUA_MACHINE_H -#define CLUA_MACHINE_H - -extern "C" { -#include -} - -/// \file -/// \brief Cartesi machine Lua interface +#ifndef JSONRPC_FORK_RESULT +#define JSONRPC_FORK_RESULT namespace cartesi { -/// \brief Initialize Cartesi machine Lua interface -/// \param L Lua state -/// \param ctxidx Index of Clua context -int clua_machine_init(lua_State *L, int ctxidx); - -/// \brief Exports symbols to table on top of Lua stack -/// \param L Lua state -/// \param ctxidx Index of Clua context -int clua_machine_export(lua_State *L, int ctxidx); +struct fork_result final { + std::string address{}; + uint32_t pid{}; +}; } // namespace cartesi -#endif +#endif // JSONRPC_FORK_RESULT diff --git a/src/jsonrpc-machine-c-api.cpp b/src/jsonrpc-machine-c-api.cpp index 18abc0dd4..5a8b042fb 100644 --- a/src/jsonrpc-machine-c-api.cpp +++ b/src/jsonrpc-machine-c-api.cpp @@ -22,17 +22,12 @@ #include #include #include -#include #include #include -#include -#include -#include #include "access-log.h" #include "i-virtual-machine.h" #include "json-util.h" -#include "jsonrpc-connection.h" #include "jsonrpc-machine-c-api.h" #include "jsonrpc-virtual-machine.h" #include "machine-c-api-internal.h" @@ -44,436 +39,226 @@ #include "os.h" #include "semantic-version.h" -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#include "asio-config.h" -#include -#include -#pragma GCC diagnostic pop - using namespace std::string_literals; -static const cartesi::jsonrpc_connection_ptr *convert_from_c(const cm_jsonrpc_connection *con) { - if (con == nullptr) { - throw std::invalid_argument("invalid connection"); +static cartesi::jsonrpc_virtual_machine::cleanup_call convert_from_c(cm_jsonrpc_cleanup_call call) { + switch (call) { + case CM_JSONRPC_DESTROY: + return cartesi::jsonrpc_virtual_machine::cleanup_call::destroy; + case CM_JSONRPC_SHUTDOWN: + return cartesi::jsonrpc_virtual_machine::cleanup_call::shutdown; + case CM_JSONRPC_NOTHING: + return cartesi::jsonrpc_virtual_machine::cleanup_call::nothing; + default: + throw std::invalid_argument("invalid cleanup call"); } - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - return reinterpret_cast(con); } -static cartesi::i_virtual_machine *convert_from_c(cm_machine *m) { - if (m == nullptr) { - throw std::invalid_argument("invalid machine"); +static cm_jsonrpc_cleanup_call convert_to_c(cartesi::jsonrpc_virtual_machine::cleanup_call call) { + switch (call) { + case cartesi::jsonrpc_virtual_machine::cleanup_call::destroy: + return CM_JSONRPC_DESTROY; + case cartesi::jsonrpc_virtual_machine::cleanup_call::shutdown: + return CM_JSONRPC_SHUTDOWN; + case cartesi::jsonrpc_virtual_machine::cleanup_call::nothing: + return CM_JSONRPC_NOTHING; + default: + throw std::invalid_argument("invalid cleanup call"); } - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - return reinterpret_cast(m); } -cm_error cm_jsonrpc_connect(const char *address, int detach_server, cm_jsonrpc_connection **con) try { - if (address == nullptr) { - throw std::invalid_argument("invalid address"); - } - if (con == nullptr) { - throw std::invalid_argument("invalid connection output"); +static cartesi::jsonrpc_virtual_machine *convert_from_c(cm_machine *m) { + if (m == nullptr) { + throw std::invalid_argument("invalid machine"); } - auto *cpp_con = - new cartesi::jsonrpc_connection_ptr(std::make_shared(address, detach_server)); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - *con = reinterpret_cast(cpp_con); - return cm_result_success(); -} catch (...) { - if (con) { - *con = nullptr; + auto *cpp_m = reinterpret_cast(m); + if (!cpp_m->is_jsonrpc_virtual_machine()) { + throw std::invalid_argument("not a JSONRPC remote machine"); } - return cm_result_failure(); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return reinterpret_cast(m); } -static boost::asio::ip::tcp::endpoint address_to_endpoint(const std::string &address) { - try { - const auto pos = address.find_last_of(':'); - const std::string ip = address.substr(0, pos); - const int port = std::stoi(address.substr(pos + 1)); - if (port < 0 || port > 65535) { - throw std::runtime_error{"invalid port"}; - } - return {boost::asio::ip::make_address(ip), static_cast(port)}; - } catch (std::exception &e) { - throw std::runtime_error{"invalid endpoint address \"" + address + "\""}; - } +static const cartesi::jsonrpc_virtual_machine *convert_from_c(const cm_machine *m) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + return convert_from_c(const_cast(m)); } -static std::string endpoint_to_string(const boost::asio::ip::tcp::endpoint &endpoint) { - std::ostringstream ss; - ss << endpoint; - return ss.str(); +static cm_machine *convert_to_c(cartesi::i_virtual_machine *cpp_m) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return reinterpret_cast(cpp_m); } -cm_error cm_jsonrpc_spawn_server(const char *address, int detach_server, cm_jsonrpc_connection **con, - const char **bound_address, int32_t *pid) try { - // this function first blocks SIGUSR1, SIGUSR2 and SIGALRM. - // then it double-forks. - // the grand-child sends the parent a SIGUSR2 and suicides if failed before execing jsonrpc-remote-cartesi-machine. - // otherwise, jsonrpc-remote-cartesi-machine itself sends the parent a SIGUSR1 to notify it is ready. - // the parent sets up to receive a SIGALRM after 15 seconds and then waits for SIGUSR1, SIGUSR2 or SIGALRM - // if it gets SIGALRM, the grand-child is unresponsive, so the parent kills it and cm_jsonrpc_spawn fails. - // if it gets SIGUSR2, the grand-child failed before exec and suicided, so cm_jsonrpc_spawn fails. - // if it gets SIGUSR1, jsonrpc-remote-cartesi-machine is ready and cm_jsonrpc_span succeeds. +cm_error cm_jsonrpc_connect_server(const char *address, cm_machine **new_m) try { + using namespace cartesi; if (address == nullptr) { throw std::invalid_argument("invalid address"); } - if (con == nullptr) { - throw std::invalid_argument("invalid connection output"); - } - if (bound_address == nullptr) { - throw std::invalid_argument("invalid bound address output"); - } - if (pid == nullptr) { - throw std::invalid_argument("invalid pid output"); - } - sigset_t mask{}; - sigset_t omask{}; - sigemptyset(&mask); // always returns 0 - sigaddset(&mask, SIGUSR1); // always returns 0 - sigaddset(&mask, SIGUSR2); // always returns 0 - sigaddset(&mask, SIGALRM); // always returns 0 - if (sigprocmask(SIG_BLOCK, &mask, &omask) < 0) { - // sigprocmask can only fail if we screwed up the values. this can't happen. - // being paranoid, if it *did* happen, we are trying to avoid a situation where - // our process gets killed when the grand-child or the alarm tries to signal us - // and the signals are not blocked - throw std::system_error{errno, std::generic_category(), "sigprocmask failed"}; - } - bool restore_sigprocmask = true; - boost::asio::io_context ioc{1}; - boost::asio::ip::tcp::acceptor a(ioc, address_to_endpoint(address)); - // already done by constructor - // a.open(endpoint.protocol()); - // a.set_option(asio::socket_base::reuse_address(true)); - // a.bind(endpoint); - // a.listen(asio::socket_base::max_listen_connections); - const char *bin = getenv("JSONRPC_REMOTE_CARTESI_MACHINE"); - if (!bin) { - bin = "jsonrpc-remote-cartesi-machine"; - } - auto ppid = getpid(); - bool restore_grand_child = false; - const int32_t grand_child = cartesi::os_double_fork_or_throw(true); - if (grand_child == 0) { // grand-child and double-fork() succeeded - sigprocmask(SIG_SETMASK, &omask, nullptr); - char sigusr1[256] = ""; - (void) snprintf(sigusr1, std::size(sigusr1), "--sigusr1=%d", ppid); - char server_fd[256] = ""; - (void) snprintf(server_fd, std::size(server_fd), "--server-fd=%d", a.native_handle()); - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) - char *args[] = {const_cast(bin), server_fd, sigusr1, nullptr}; - if (execvp(bin, args) < 0) { - // here we failed to run jsonrpc-remote-cartesi-machine. nothing we can do. - kill(ppid, SIGUSR2); // notify parent as soon as possible that we failed. - exit(1); - }; - return cm_result_success(); // code never reaches here - } else if (grand_child > 0) { // parent and double-fork() succeeded - restore_grand_child = true; // make sure grand-child is killed if we fail - static THREAD_LOCAL std::string bound_address_storage = endpoint_to_string(a.local_endpoint()); - a.close(); - struct itimerval ovalue {}; - bool restore_itimer = false; - try { - struct itimerval value {}; - memset(&value, 0, sizeof(value)); - value.it_interval.tv_sec = 0; - value.it_interval.tv_usec = 0; - value.it_value.tv_sec = 15; - value.it_value.tv_usec = 0; - if (setitimer(ITIMER_REAL, &value, &ovalue) < 0) { - // setitimer only fails if we screwed up with the values. this should not happen. - // being paranoid, if it *did* happen, and if the grand-child also failed to signal us, - // we might hang forever in the following call to sigwait. - // we prefer to give up instead of risking a deadlock. - throw std::system_error{errno, std::generic_category(), "setitimer failed"}; - } - restore_itimer = true; - int sig = 0; - if (auto ret = sigwait(&mask, &sig); ret != 0) { - throw std::system_error{ret, std::generic_category(), "sigwait failed"}; - } - if (sig == SIGALRM) { // grand-child didn't signal us before alarm - throw std::runtime_error{"grand-child process unresponsive"}; - } - if (sig == SIGUSR2) { // grand-child signaled us that it failed to exec - // grand-child will have exited on its own - restore_grand_child = false; - throw std::runtime_error{"failed to run '"s + bin + "'"s}; - } - // grand-child signaled us that everything is fine - assert(sig == SIGUSR1); - setitimer(ITIMER_REAL, &ovalue, nullptr); - restore_itimer = false; - sigprocmask(SIG_SETMASK, &omask, nullptr); - restore_sigprocmask = false; - *bound_address = bound_address_storage.c_str(); - *pid = grand_child; - auto ret = cm_jsonrpc_connect(*bound_address, detach_server, con); - if (ret < 0) { // and yet we failed to connect - kill(grand_child, SIGTERM); - *bound_address = nullptr; - *pid = 0; - } - return ret; - } catch (...) { - if (restore_sigprocmask) { - sigprocmask(SIG_SETMASK, &omask, nullptr); - } - if (restore_grand_child) { - kill(grand_child, SIGTERM); - } - if (restore_itimer) { - setitimer(ITIMER_REAL, &ovalue, nullptr); - } - *con = nullptr; - *bound_address = nullptr; - *pid = 0; - return cm_result_failure(); - } - } - return cm_result_success(); // code never reaches here -} catch (...) { - *con = nullptr; - *bound_address = nullptr; - *pid = 0; - return cm_result_failure(); -} - -void cm_jsonrpc_release_connection(const cm_jsonrpc_connection *con) { - if (con == nullptr) { - return; - } - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - const auto *cpp_con = reinterpret_cast(con); - delete cpp_con; -} - -cm_error cm_jsonrpc_create_machine(const cm_jsonrpc_connection *con, int detach_machine, const char *config, - const char *runtime_config, cm_machine **new_machine) try { - if (new_machine == nullptr) { + if (new_m == nullptr) { throw std::invalid_argument("invalid new machine output"); } - const cartesi::machine_config c = cartesi::from_json(config); - cartesi::machine_runtime_config r; - if (runtime_config) { - r = cartesi::from_json(runtime_config); - } - const auto *cpp_con = convert_from_c(con); - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - *new_machine = reinterpret_cast(new cartesi::jsonrpc_virtual_machine(*cpp_con, detach_machine, c, r)); + *new_m = convert_to_c(new jsonrpc_virtual_machine(address)); return cm_result_success(); } catch (...) { - if (new_machine) { - *new_machine = nullptr; + if (new_m) { + *new_m = nullptr; } return cm_result_failure(); } -cm_error cm_jsonrpc_load_machine(const cm_jsonrpc_connection *con, int detach_machine, const char *dir, - const char *runtime_config, cm_machine **new_machine) try { - if (new_machine == nullptr) { +cm_error cm_jsonrpc_spawn_server(const char *address, cm_machine **new_m, const char **bound_address, + uint32_t *pid) try { + using namespace cartesi; + if (address == nullptr) { + throw std::invalid_argument("invalid address"); + } + if (new_m == nullptr) { throw std::invalid_argument("invalid new machine output"); } - if (dir == nullptr) { - throw std::invalid_argument("invalid dir"); + fork_result spawned; + *new_m = convert_to_c(new jsonrpc_virtual_machine(address, spawned)); + if (bound_address) { + *bound_address = cm_set_temp_string(spawned.address); } - cartesi::machine_runtime_config r; - if (runtime_config) { - r = cartesi::from_json(runtime_config); + if (pid) { + *pid = spawned.pid; } - const auto *cpp_con = convert_from_c(con); - *new_machine = - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - reinterpret_cast(new cartesi::jsonrpc_virtual_machine(*cpp_con, detach_machine, dir, r)); return cm_result_success(); } catch (...) { - if (new_machine) { - *new_machine = nullptr; + if (new_m) { + *new_m = nullptr; } - return cm_result_failure(); -} - -cm_error cm_jsonrpc_destroy_machine(cm_machine *m) try { - if (m != nullptr) { - auto *cpp_machine = convert_from_c(m); - cpp_machine->destroy(); - delete cpp_machine; + if (bound_address) { + *bound_address = nullptr; + } + if (pid) { + *pid = 0; } - return cm_result_success(); -} catch (...) { return cm_result_failure(); } -cm_error cm_jsonrpc_get_machine(const cm_jsonrpc_connection *con, int detach_machine, cm_machine **new_machine) try { - if (new_machine == nullptr) { - throw std::invalid_argument("invalid new machine output"); +cm_error cm_jsonrpc_fork_server(const cm_machine *m, cm_machine **forked_m, const char **address, uint32_t *pid) try { + using namespace cartesi; + if (address == nullptr) { + throw std::invalid_argument("invalid address output"); } - const auto *cpp_con = convert_from_c(con); - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - *new_machine = reinterpret_cast(new cartesi::jsonrpc_virtual_machine(*cpp_con, detach_machine)); + const auto *cpp_m = convert_from_c(m); + const auto forked = cpp_m->fork_server(); + *address = cm_set_temp_string(forked.address); + if (pid) { + *pid = static_cast(forked.pid); + } + *forked_m = convert_to_c(new jsonrpc_virtual_machine(forked.address)); return cm_result_success(); } catch (...) { - if (new_machine) { - *new_machine = nullptr; + if (address) { + *address = nullptr; + } + if (pid) { + *pid = 0; } return cm_result_failure(); } -CM_API cm_error cm_jsonrpc_get_connection(cm_machine *m, const cm_jsonrpc_connection **con) try { - auto *cpp_machine = convert_from_c(m); - cartesi::jsonrpc_virtual_machine *cpp_json_machine = dynamic_cast(cpp_machine); - if (!cpp_json_machine) { - throw std::invalid_argument("not a remote machine"); +cm_error cm_jsonrpc_rebind_server(cm_machine *m, const char *address, const char **address_bound) try { + auto *cpp_m = convert_from_c(m); + const auto cpp_address_bound = cpp_m->rebind_server(address); + if (address_bound) { + *address_bound = cm_set_temp_string(cpp_address_bound); } - auto *cpp_con = new cartesi::jsonrpc_connection_ptr(cpp_json_machine->get_connection()); - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - *con = reinterpret_cast(cpp_con); return cm_result_success(); } catch (...) { - *con = nullptr; + if (address_bound) { + *address_bound = nullptr; + } return cm_result_failure(); } -cm_error cm_jsonrpc_get_default_config(const cm_jsonrpc_connection *con, const char **config) try { - if (config == nullptr) { - throw std::invalid_argument("invalid config output"); +cm_error cm_jsonrpc_get_server_version(const cm_machine *m, const char **version) try { + if (version == nullptr) { + throw std::invalid_argument("invalid version output"); } - const auto *cpp_con = convert_from_c(con); - const cartesi::machine_config cpp_config = cartesi::jsonrpc_virtual_machine::get_default_config(*cpp_con); - *config = cm_set_temp_string(cartesi::to_json(cpp_config).dump()); + const auto *cpp_m = convert_from_c(m); + const auto cpp_version = cpp_m->get_server_version(); + *version = cm_set_temp_string(cartesi::to_json(cpp_version).dump()); return cm_result_success(); } catch (...) { - if (config) { - *config = nullptr; + if (version) { + *version = nullptr; } return cm_result_failure(); } -cm_error cm_jsonrpc_verify_step_uarch(const cm_jsonrpc_connection *con, const cm_hash *root_hash_before, - const char *log, const cm_hash *root_hash_after) try { - if (log == nullptr) { - throw std::invalid_argument("invalid access log"); - } - const auto *cpp_con = convert_from_c(con); - const auto cpp_log = // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - cartesi::from_json>(log).value(); - const cartesi::machine::hash_type cpp_root_hash_before = convert_from_c(root_hash_before); - const cartesi::machine::hash_type cpp_root_hash_after = convert_from_c(root_hash_after); - cartesi::jsonrpc_virtual_machine::verify_step_uarch(*cpp_con, cpp_root_hash_before, cpp_log, cpp_root_hash_after); +cm_error cm_jsonrpc_shutdown_server(cm_machine *m) try { + auto *cpp_m = convert_from_c(m); + cpp_m->shutdown_server(); return cm_result_success(); } catch (...) { return cm_result_failure(); } -cm_error cm_jsonrpc_verify_reset_uarch(const cm_jsonrpc_connection *con, const cm_hash *root_hash_before, - const char *log, const cm_hash *root_hash_after) try { - if (log == nullptr) { - throw std::invalid_argument("invalid access log"); - } - const auto *cpp_con = convert_from_c(con); - const auto cpp_log = // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - cartesi::from_json>(log).value(); - const cartesi::machine::hash_type cpp_root_hash_before = convert_from_c(root_hash_before); - const cartesi::machine::hash_type cpp_root_hash_after = convert_from_c(root_hash_after); - cartesi::jsonrpc_virtual_machine::verify_reset_uarch(*cpp_con, cpp_root_hash_before, cpp_log, cpp_root_hash_after); +cm_error cm_jsonrpc_delay_next_request(cm_machine *m, uint64_t ms) try { + auto *cpp_m = convert_from_c(m); + cpp_m->delay_next_request(ms); return cm_result_success(); } catch (...) { return cm_result_failure(); } -cm_error cm_jsonrpc_fork_server(const cm_jsonrpc_connection *con, const char **address, int32_t *pid) try { - if (address == nullptr) { - throw std::invalid_argument("invalid address output"); - } - const auto *cpp_con = convert_from_c(con); - const auto result = (*cpp_con)->fork_server(); - *address = cm_set_temp_string(result.address); - if (pid) { - *pid = static_cast(result.pid); - } +cm_error cm_jsonrpc_set_timeout(cm_machine *m, int64_t ms) try { + auto *cpp_m = convert_from_c(m); + cpp_m->set_timeout(ms); return cm_result_success(); } catch (...) { - if (address) { - *address = nullptr; - } - if (pid) { - *pid = 0; - } return cm_result_failure(); } -cm_error cm_jsonrpc_rebind_server(const cm_jsonrpc_connection *con, const char *address, const char **new_address) try { - const auto *cpp_con = convert_from_c(con); - const std::string cpp_new_address = (*cpp_con)->rebind_server(address); - if (new_address) { - *new_address = cm_set_temp_string(cpp_new_address); +cm_error cm_jsonrpc_get_timeout(cm_machine *m, int64_t *ms) try { + if (ms == nullptr) { + throw std::invalid_argument("invalid ms output"); } + auto *cpp_m = convert_from_c(m); + *ms = cpp_m->get_timeout(); return cm_result_success(); } catch (...) { - if (new_address) { - *new_address = nullptr; - } return cm_result_failure(); } -cm_error cm_jsonrpc_get_reg_address(const cm_jsonrpc_connection *con, cm_reg reg, uint64_t *val) try { - if (val == nullptr) { - throw std::invalid_argument("invalid val output"); - } - const auto *cpp_con = convert_from_c(con); - const auto cpp_reg = static_cast(reg); - *val = cartesi::jsonrpc_virtual_machine::get_reg_address(*cpp_con, cpp_reg); +cm_error cm_jsonrpc_set_cleanup_call(cm_machine *m, cm_jsonrpc_cleanup_call call) try { + auto *cpp_m = convert_from_c(m); + auto cpp_call = convert_from_c(call); + cpp_m->set_cleanup_call(cpp_call); return cm_result_success(); } catch (...) { - if (val) { - *val = 0; - } return cm_result_failure(); } -cm_error cm_jsonrpc_get_server_version(const cm_jsonrpc_connection *con, const char **version) try { - if (version == nullptr) { - throw std::invalid_argument("invalid version output"); +cm_error cm_jsonrpc_get_cleanup_call(cm_machine *m, cm_jsonrpc_cleanup_call *call) try { + if (call == nullptr) { + throw std::invalid_argument("invalid call output"); } - const auto *cpp_con = convert_from_c(con); - const cartesi::semantic_version cpp_version = (*cpp_con)->get_server_version(); - *version = cm_set_temp_string(cartesi::to_json(cpp_version).dump()); + auto *cpp_m = convert_from_c(m); + *call = convert_to_c(cpp_m->get_cleanup_call()); return cm_result_success(); } catch (...) { - if (version) { - *version = nullptr; - } return cm_result_failure(); } -cm_error cm_jsonrpc_shutdown_server(const cm_jsonrpc_connection *con) try { - if (con != nullptr) { - const auto *cpp_con = convert_from_c(con); - (*cpp_con)->shutdown_server(); - delete cpp_con; +cm_error cm_jsonrpc_get_server_address(cm_machine *m, const char **address) try { + if (address == nullptr) { + throw std::invalid_argument("invalid address output"); } + auto *cpp_m = convert_from_c(m); + *address = cm_set_temp_string(cpp_m->get_server_address()); return cm_result_success(); } catch (...) { return cm_result_failure(); } -cm_error cm_jsonrpc_verify_send_cmio_response(const cm_jsonrpc_connection *con, uint16_t reason, const uint8_t *data, - uint64_t length, const cm_hash *root_hash_before, const char *log, const cm_hash *root_hash_after) try { - if (log == nullptr) { - throw std::invalid_argument("invalid access log"); - } - const auto *cpp_con = convert_from_c(con); - const auto cpp_log = // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - cartesi::from_json>(log).value(); - const cartesi::machine::hash_type cpp_root_hash_before = convert_from_c(root_hash_before); - const cartesi::machine::hash_type cpp_root_hash_after = convert_from_c(root_hash_after); - cartesi::jsonrpc_virtual_machine::verify_send_cmio_response(*cpp_con, reason, data, length, cpp_root_hash_before, - cpp_log, cpp_root_hash_after); +cm_error cm_jsonrpc_emancipate_server(cm_machine *m) try { + auto *cpp_m = convert_from_c(m); + cpp_m->emancipate_server(); return cm_result_success(); } catch (...) { return cm_result_failure(); diff --git a/src/jsonrpc-machine-c-api.h b/src/jsonrpc-machine-c-api.h index f87f0fafc..abfbf43ef 100644 --- a/src/jsonrpc-machine-c-api.h +++ b/src/jsonrpc-machine-c-api.h @@ -24,189 +24,148 @@ extern "C" { #endif // ----------------------------------------------------------------------------- -// API enums and structures +// API enums // ----------------------------------------------------------------------------- -/// \brief Handle of the JSONRPC connection. -/// \details It's used only as an opaque handle to pass JSONRPC connection through the C API. -typedef struct cm_jsonrpc_connection cm_jsonrpc_connection; +/// \brief Resources to cleanup when machine object is deleted +typedef enum cm_jsonrpc_cleanup_call { + CM_JSONRPC_NOTHING, ///< Just delete object + CM_JSONRPC_DESTROY, ///< Implicitly call cm_destroy() + CM_JSONRPC_SHUTDOWN ///< Implicitly call cm_jsonrpc_shutdown_server() +} cm_jsonrpc_cleanup_call; // ----------------------------------------------------------------------------- -// API functions +// Server API functions // ----------------------------------------------------------------------------- -// ------------------------------------ -// Remote server management -// ------------------------------------ - -/// \brief Connects to an existing JSONRPC remote machine server. +/// \brief Spawns a new remote machine server. +/// \param address Address (in local host) to bind the new remote machine server. +/// \param new_m Receives the pointer to the new JSONRPC remote machine object. Set to NULL on failure. +/// \param bound_address_bound Receives the address that the remote machine server actually bound to, +/// guaranteed to remain valid only until the next CM_API function is called again on the same thread. +/// Set to NULL on failure. +/// \param pid Receives the spawned server process id. Set to 0 on failure. +/// \returns 0 for success, non zero code for error. +/// \details A newly spawned remote machine server does not hold a machine instance. +/// Use cm_create() or cm_load() to instantiate a machine into the object. +/// Use cm_delete() to delete the object. +/// \details The spawned process is in the process group of the caller. +/// Use cm_jsonrpc_emancipate_server() to make it leader of its own process group. +/// \details The machine object is not configured to implicitly cleanup anything on cm_delete(). +/// Use cm_jsonrpc_set_cleanup_call() to change this setting. +/// \details Unless the desired jsonrpc-remote-cartesi-machine executable is in the path, +/// the environment variable JSONRPC_REMOTE_CARTESI_MACHINE must point directly to the executable. +CM_API cm_error cm_jsonrpc_spawn_server(const char *address, cm_machine **new_m, const char **bound_address, + uint32_t *pid); + +/// \brief Connects to an existing remote machine server. /// \param address Address of the remote machine server to connect to. -/// \param detach_server When non-zero, do not implicitly call -/// cm_jsonrpc_shutdown_server() server on cm_jsonrpc_release_connection(). -/// \param con If function succeeds, receives new JSONRPC connection. Set to NULL on failure. -/// \returns 0 for success, non zero code for error. -CM_API cm_error cm_jsonrpc_connect(const char *address, int detach_server, cm_jsonrpc_connection **con); - -/// \brief Spawns a new JSONRPC remote machine server and connect to it. -/// \param address Address (in local host) to bind the new JSONRPC remote machine server. -/// \param detach_server When non-zero, do not implicitly call -/// cm_jsonrpc_shutdown_server() server on cm_jsonrpc_release_connection(). -/// \param con If function succeeds, receives new JSONRPC connection. Set to NULL on failure. -/// \param bound_address_bound Receives the address that the remote server -/// actually bound to, guaranteed to remain valid only until the next CM_API -/// function is called again on the same thread. Set to NULL on failure. -/// \param pid If function suceeds, receives the forked child process id. Set to 0 on failure. -/// \details If the jsonrpc-remote-cartesi-machine executable is not in the path, -/// the environment variable JSONRPC_REMOTE_CARTESI_MACHINE should point to the executable. -CM_API cm_error cm_jsonrpc_spawn_server(const char *address, int detach_server, cm_jsonrpc_connection **con, - const char **bound_address, int32_t *pid); - -/// \brief Releases a reference to a JSONRPC connection to remote machine server. -/// \param con Pointer a JSONRPC connection (can be NULL). -/// \returns 0 for success, non zero code for error. -/// \details When the last reference to the connection is released, if the -/// server is not detached, implicitly call cm_jsonrpc_shutdown_server() to -/// shutdown the server. This might fail silently. To know if the shutdown of -/// a server was successful, call cm_jsonrpc_shutdown_server() explicitly. -/// The connection pointer must not be used after this call. -CM_API void cm_jsonrpc_release_connection(const cm_jsonrpc_connection *con); - -/// \brief Forks the remote server. -/// \param con Pointer to a valid JSONRPC connection. -/// \param address If function succeeds, receives address of new server, guaranteed to remain valid -/// only until the next CM_API function is called again on the same thread. Set to NULL on failure. +/// \param new_m Receives the pointer to the new JSONRPC remote machine object. Set to NULL on failure. +/// \returns 0 for success, non zero code for error. +/// \details The machine object is not configured to implicitly cleanup anything on cm_delete(). +/// Use cm_jsonrpc_set_cleanup_call() to change this setting. +/// \details If the remote machine server already holds a machine instance, it is ready for use. +/// Otherwise, use cm_create() or cm_load() to instantiate a machine into the object. +/// Use cm_delete() to delete the object. +CM_API cm_error cm_jsonrpc_connect_server(const char *address, cm_machine **new_m); + +/// \brief Forks the remote machine server. +/// \param m Pointer to a valid JSONRPC remote machine object. +/// \param forked_m Receives the pointer to the forked JSONRPC remote machine object. Set to NULL on failure. +/// \param address If function succeeds, receives address the forked server bound to, +/// guaranteed to remain valid only until the next CM_API function is called again on the same thread. +/// Set to NULL on failure. /// \param pid If function suceeds, receives the forked child process id (can be NULL). Set to 0 on failure. /// \returns 0 for success, non zero code for error. -CM_API cm_error cm_jsonrpc_fork_server(const cm_jsonrpc_connection *con, const char **address, int32_t *pid); - -/// \brief Shutdowns remote server. -/// \param con Pointer to a valid JSONRPC connection (can be NULL). -/// \returns 0 for success, non zero code for error. -/// \details If the call succeeds, the pointer to the JSONRPC connection must not be used after this call. -CM_API cm_error cm_jsonrpc_shutdown_server(const cm_jsonrpc_connection *con); - -/// \brief Changes the address the remote server is listening to. -/// \param con Pointer to a valid JSONRPC connection. -/// \param address Address the remote server should bind to. -/// \param address_bound Receives the address that the remote server actually bound to, guaranteed to remain valid -/// only until the next CM_API function is called again on the same thread. Set to NULL on failure. -/// \returns 0 for success, non zero code for error. -CM_API cm_error cm_jsonrpc_rebind_server(const cm_jsonrpc_connection *con, const char *address, - const char **address_bound); - -/// \brief Gets the semantic version of the remote server. -/// \param con Pointer to a valid JSONRPC connection. +/// \details If the remote machine server already holds a machine instance, the forked copy is ready for use. +/// Otherwise, use cm_create() or cm_load() to instantiate a machine into the forked server object. +/// Use cm_delete() to delete the object. +/// \details The forked process is in the process group of the remote server. +/// Use cm_jsonrpc_emancipate_server() to make it leader of its own process group. +/// \details The machine object is not configured to implicitly cleanup anything on cm_delete(). +/// Use cm_jsonrpc_set_cleanup_call() to change this setting. +/// \warning If the server is running on a remote host, the \p pid is also remote and cannot be signaled. +/// Trying to do so may signal an entirely unrelated process in the local host. +CM_API cm_error cm_jsonrpc_fork_server(const cm_machine *m, cm_machine **forked_m, const char **address, uint32_t *pid); + +/// \brief Shuts down the remote machine server. +/// \param m Pointer to a valid JSONRPC remote machine object. +/// \returns 0 for success, non zero code for error. +/// \details cm_delete() may fail silently when implicitly calling cm_jsonrpc_shutdown_server(). +/// To make sure the server was successfully shutdown, call cm_jsonrpc_shutdown_server() explicitly. +/// \details This function does not delete the machine object. +/// You must still call cm_delete() afterwards. +CM_API cm_error cm_jsonrpc_shutdown_server(cm_machine *m); + +/// \brief Changes the address the remote machine server is listening to. +/// \param m Pointer to a valid JSONRPC remote machine object. +/// \param address Address the remote machine server should bind to. +/// \param address_bound Receives the address that the remote machine server actually bound to, +/// guaranteed to remain valid only until the next CM_API function is called again on the same thread. +/// Set to NULL on failure. +/// \returns 0 for success, non zero code for error. +/// \detail The function automatically updates the address the machine object uses to communicate with the server. +CM_API cm_error cm_jsonrpc_rebind_server(cm_machine *m, const char *address, const char **address_bound); + +/// \brief Gets the semantic version of the remote machine server. +/// \param m Pointer to a valid JSONRPC remote machine object. /// \param semantic_version Receives the semantic version as a JSON object in a string, -/// guaranteed to remain valid only until the next CM_API function is called again on the -/// same thread. Set to NULL on failure. +/// guaranteed to remain valid only until the next CM_API function is called again on the same thread. +/// Set to NULL on failure. /// \returns 0 for success, non zero code for error. -CM_API cm_error cm_jsonrpc_get_server_version(const cm_jsonrpc_connection *con, const char **version); +CM_API cm_error cm_jsonrpc_get_server_version(const cm_machine *m, const char **version); -/// \brief Returns a JSON object in a string with the default machine config from the remote server. -/// \param con Pointer to a valid JSONRPC connection. -/// \param config Receives the default configuration, guaranteed to remain valid only until -/// the next CM_API function is called again on the same thread. -/// \returns 0 for success, non zero code for error. -/// \details The returned config is not sufficient to run a machine. -/// Additional configurations, such as RAM length, RAM image, flash drives, -/// and entrypoint are still needed. -CM_API cm_error cm_jsonrpc_get_default_config(const cm_jsonrpc_connection *con, const char **config); - -/// \brief Gets the address of any x, f, or control state register from the remote server. -/// \param con Pointer to a valid JSONRPC connection. -/// \param reg The register. -/// \param val Receives address of the register. -/// \returns 0 for success, non zero code for error. -CM_API cm_error cm_jsonrpc_get_reg_address(const cm_jsonrpc_connection *con, cm_reg reg, uint64_t *val); - -// ------------------------------------ -// Machine API functions -// ------------------------------------ - -/// \brief Creates a remote machine instance. -/// \param con Pointer to a valid JSONRPC connection. -/// \param detach_machine When non-zero, cm_destroy() does not attempt to destroy the remote machine. -/// \param config Machine configuration as a JSON string. -/// \param runtime_config Machine runtime configuration as a JSON string (can be NULL). -/// \param m Receives the pointer to new remote machine instance. +/// \brief Breaks server out of parent program group. +/// \param m Pointer to a valid JSONRPC remote machine object. /// \returns 0 for success, non zero code for error. -/// \details The machine instance holds its own reference to the connection. -/// \details cm_destroy() might fail silently when attempting to destroy an -/// attached remote machine. To make sure the destruction of a remote machine was -/// successful, call cm_jsonrpc_destroy_machine() instead. -CM_API cm_error cm_jsonrpc_create_machine(const cm_jsonrpc_connection *con, int detach_machine, const char *config, - const char *runtime_config, cm_machine **m); - -/// \brief Creates a remote machine instance from previously stored directory in the remote server. -/// \param con Pointer to a valid JSONRPC connection. -/// \param detach_machine When non-zero, cm_destroy() does not attempt to destroy the remote machine. -/// \param dir Directory where previous machine is stored. -/// \param runtime_config Machine runtime configuration as a JSON string (can be NULL). -/// \param m Receives the pointer to new remote machine instance. -/// \returns 0 for success, non zero code for error. -/// \details The machine instance holds its own reference to the connection. -/// cm_destroy() might fail silently when attempting to destroy an attached -/// remote machine. To make sure the destruction of a remote machine was -/// successful, call cm_jsonrpc_destroy_machine() instead. -CM_API cm_error cm_jsonrpc_load_machine(const cm_jsonrpc_connection *con, int detach_machine, const char *dir, - const char *runtime_config, cm_machine **m); - -/// \brief Destroy a machine, whether it is attached or not, and releases handle. -/// \param m Pointer to the existing machine handle (can be NULL). -/// \returns 0 for success, non zero code for error. -/// \details Local machines are always destroyed sucessfully. -/// In contrast, the attempt to destroy a remote machine might fail. -/// If the call succeeds, the machine handle must not be used after this call. -CM_API cm_error cm_jsonrpc_destroy_machine(cm_machine *m); - -/// \brief Get remote machine instance that was previously created in the remote server. -/// \param con Pointer to a valid JSONRPC connection. -/// \param detach_machine When non-zero, do not implicitly call cm_destroy_machine() on cm_release_machine(). -/// \param m Receives the pointer to remote machine instance. +/// \detail A spawned/forked server process starts in the same process group as its parent. +/// This function makes it the leader of its own program group. +CM_API cm_error cm_jsonrpc_emancipate_server(cm_machine *m); + +/// \brief Asks server to delay next request by a given amount of time. +/// \param m Pointer to a valid JSONRPC remote machine object. +/// \param ms Number of milliseconds to delay next request. /// \returns 0 for success, non zero code for error. -/// \details The machine instance holds its own reference to the connection. -CM_API cm_error cm_jsonrpc_get_machine(const cm_jsonrpc_connection *con, int detach_machine, cm_machine **m); +CM_API cm_error cm_jsonrpc_delay_next_request(cm_machine *m, uint64_t ms); + +// ----------------------------------------------------------------------------- +// Client API functions +// ----------------------------------------------------------------------------- -/// \brief Get new reference to a connection to JSONRPC server given a remote machine instance. -/// \param m Pointer to a valid machine instance. -/// \param con Receives the pointer to JSONRPC connection. Set to NULL on failure. +/// \brief Sets a timeout for communication with remote machine server. +/// \param m Pointer to a valid JSONRPC remote machine object. +/// \param ms Number of milliseconds to wait before returning with a timeout. Use -1 to block indefinitely. /// \returns 0 for success, non zero code for error. -CM_API cm_error cm_jsonrpc_get_connection(cm_machine *m, const cm_jsonrpc_connection **con); +CM_API cm_error cm_jsonrpc_set_timeout(cm_machine *m, int64_t ms); -// ------------------------------------ -// Verifying -// ------------------------------------ +/// \brief Gets the current timeout for communication with remote machine server. +/// \param m Pointer to a valid JSONRPC remote machine object. +/// \param ms Receives the number of milliseconds to wait before returning with a timeout. (-1 blocks indefinitely). +/// \returns 0 for success, non zero code for error. +CM_API cm_error cm_jsonrpc_get_timeout(cm_machine *m, int64_t *ms); -/// \brief Checks the validity of a state transition produced by cm_log_step_uarch. -/// \param con Pointer to a valid JSONRPC connection. -/// \param root_hash_before State hash before load. -/// \param log State access log to be verified as a JSON string. -/// \param root_hash_after State hash after load. +/// \brief Configures the implicit cleanup call at object deletion. +/// \param m Pointer to a valid JSONRPC remote machine object. +/// \param call If set to CM_JSONRPC_DESTROY, implicitly call cm_destroy() on cm_delete(). +/// If set to CM_JSONRPC_SHUTDOWN, implicitly call cm_jsonrpc_shutdown_server() on cm_delete(). +/// Otherwise (i.e., CM_JSONRPC_NOTHING), simply delete object on cm_delete(). +/// This is the default behavior. /// \returns 0 for success, non zero code for error. -CM_API cm_error cm_jsonrpc_verify_step_uarch(const cm_jsonrpc_connection *con, const cm_hash *root_hash_before, - const char *log, const cm_hash *root_hash_after); - -/// \brief Checks the validity of a state transition produced by cm_log_verify_reset_uarch. -/// \param con Pointer to a valid JSONRPC connection. -/// \param root_hash_before State hash before load. -/// \param log State access log to be verified as a JSON string. -/// \param root_hash_after State hash after load. +CM_API cm_error cm_jsonrpc_set_cleanup_call(cm_machine *m, cm_jsonrpc_cleanup_call call); + +/// \brief Retrieves the implicit cleanup call at object is deletion. +/// \param m Pointer to a valid JSONRPC remote machine object. +/// \param call Receives either CM_JSONRPC_NOTHING, CM_JSONRPC_DESTROY, or CM_JSONRPC_SHUTDOWN. +/// See cm_jsonrpc_set_cleanup_call(). /// \returns 0 for success, non zero code for error. -CM_API cm_error cm_jsonrpc_verify_reset_uarch(const cm_jsonrpc_connection *con, const cm_hash *root_hash_before, - const char *log, const cm_hash *root_hash_after); - -/// \brief Checks the validity of a state transition produced by cm_log_send_cmio_response. -/// \param con Pointer to a valid JSONRPC connection. -/// \param reason Reason for sending the response. -/// \param data The response sent when the log was generated. -/// \param length Length of response. -/// \param root_hash_before State hash before load. -/// \param log State access log to be verified as a JSON string. -/// \param root_hash_after State hash after load. +CM_API cm_error cm_jsonrpc_get_cleanup_call(cm_machine *m, cm_jsonrpc_cleanup_call *call); + +/// \brief Retrieves the address of remote server. +/// \param m Pointer to a valid JSONRPC remote machine object. +/// \param address Receives the address of the remote machine, guaranteed to remain valid only until +/// the next CM_API function is called again on the same thread. /// \returns 0 for success, non zero code for error. -CM_API cm_error cm_jsonrpc_verify_send_cmio_response(const cm_jsonrpc_connection *con, uint16_t reason, - const uint8_t *data, uint64_t length, const cm_hash *root_hash_before, const char *log, - const cm_hash *root_hash_after); +CM_API cm_error cm_jsonrpc_get_server_address(cm_machine *m, const char **address); #ifdef __cplusplus } diff --git a/src/jsonrpc-remote-machine.cpp b/src/jsonrpc-remote-machine.cpp index 7f6b88207..5432ddb4c 100644 --- a/src/jsonrpc-remote-machine.cpp +++ b/src/jsonrpc-remote-machine.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -29,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -259,6 +261,7 @@ struct http_handler : std::enable_shared_from_this { asio::signal_set signals; ///< Signal set used for process termination notifications tcp::endpoint local_endpoint; ///< Address server receives requests at tcp::acceptor acceptor; ///< TCP connection acceptor + uint64_t delay{0}; ///< How much to delay next request in ms std::unique_ptr machine; ///< Cartesi Machine, if any std::vector> sessions; ///< HTTP sessions @@ -764,7 +767,7 @@ static json jsonrpc_fork_handler(const json &j, const std::shared_ptrhandler->ioc.notify_fork(asio::io_context::fork_prepare); // Done initializing, so we fork const char *err_msg = nullptr; - const int pid = cartesi::os_double_fork(true, &err_msg); + const int pid = cartesi::os_double_fork(false, &err_msg); if (pid == 0) { // child // Notify to ASIO that we are the child session->handler->ioc.notify_fork(asio::io_context::fork_child); @@ -785,7 +788,10 @@ static json jsonrpc_fork_handler(const json &j, const std::shared_ptrhandler->local_endpoint << " fork failed (" << err_msg << ")"; return jsonrpc_response_server_error(j, "fork failed ("s + err_msg + ")"s); } - const cartesi::fork_result result{new_server_address, static_cast(pid)}; + const cartesi::fork_result result{ + new_server_address, + static_cast(pid), + }; return jsonrpc_response_ok(j, result); } @@ -805,21 +811,21 @@ static json jsonrpc_rebind_handler(const json &j, const std::shared_ptrhandler->rebind(tcp::acceptor{session->handler->ioc, new_local_endpoint}); SLOG(trace) << session->handler->local_endpoint << " rebound to " << session->handler->local_endpoint; } else { - SLOG(trace) << session->handler->local_endpoint << " rebind skipped"; + SLOG(trace) << session->handler->local_endpoint << " rebind unecessary"; } const std::string result = endpoint_to_string(session->handler->local_endpoint); return jsonrpc_response_ok(j, result); } -/// \brief JSONRPC handler for the machine.machine.directory method +/// \brief JSONRPC handler for the machine.load method /// \param j JSON request object /// \param session HTTP session /// \returns JSON response object -static json jsonrpc_machine_machine_directory_handler(const json &j, const std::shared_ptr &session) { +static json jsonrpc_machine_load_handler(const json &j, const std::shared_ptr &session) { if (session->handler->machine) { return jsonrpc_response_invalid_request(j, "machine exists"); } - static const char *param_name[] = {"directory", "runtime"}; + static const char *param_name[] = {"directory", "runtime_config"}; auto args = parse_args>(j, param_name); switch (count_args(args)) { case 1: @@ -835,15 +841,15 @@ static json jsonrpc_machine_machine_directory_handler(const json &j, const std:: return jsonrpc_response_ok(j); } -/// \brief JSONRPC handler for the machine.machine.config method +/// \brief JSONRPC handler for the machine.create method /// \param j JSON request object /// \param session HTTP session /// \returns JSON response object -static json jsonrpc_machine_machine_config_handler(const json &j, const std::shared_ptr &session) { +static json jsonrpc_machine_create_handler(const json &j, const std::shared_ptr &session) { if (session->handler->machine) { return jsonrpc_response_invalid_request(j, "machine exists"); } - static const char *param_name[] = {"config", "runtime"}; + static const char *param_name[] = {"config", "runtime_config"}; auto args = parse_args>(j, param_name); switch (count_args(args)) { @@ -870,6 +876,38 @@ static json jsonrpc_machine_destroy_handler(const json &j, const std::shared_ptr return jsonrpc_response_ok(j); } +/// \brief JSONRPC handler for the delay_next_request method +/// \param j JSON request object +/// \param session HTTP session +/// \returns JSON response object +/// \details This method causes the server to sleep for a number of miliseconds before the next call +static json jsonrpc_delay_next_request_handler(const json &j, const std::shared_ptr &session) { + static const char *param_name[] = {"ms"}; + auto args = parse_args(j, param_name); + session->handler->delay = std::get<0>(args); + return jsonrpc_response_ok(j); +} + +/// \brief JSONRPC handler for the machine.destroy method +/// \param j JSON request object +/// \param session HTTP session +/// \returns JSON response object +static json jsonrpc_machine_is_empty_handler(const json &j, const std::shared_ptr &session) { + jsonrpc_check_no_params(j); + return jsonrpc_response_ok(j, session->handler->machine == nullptr); +} + +/// \brief JSONRPC handler for the emancipate method +/// \param j JSON request object +/// \param session HTTP session +/// \returns JSON response object +static json jsonrpc_emancipate_handler(const json &j, const std::shared_ptr &session) { + (void) session; + jsonrpc_check_no_params(j); + setpgid(0, 0); + return jsonrpc_response_ok(j); +} + /// \brief JSONRPC handler for the machine.store method /// \param j JSON request object /// \param session HTTP session @@ -1223,6 +1261,32 @@ static json jsonrpc_machine_get_initial_config_handler(const json &j, const std: return jsonrpc_response_ok(j, session->handler->machine->get_initial_config()); } +/// \brief JSONRPC handler for the machine.get_runtime_config method +/// \param j JSON request object +/// \param session HTTP session +/// \returns JSON response object +static json jsonrpc_machine_get_runtime_config_handler(const json &j, const std::shared_ptr &session) { + if (!session->handler->machine) { + return jsonrpc_response_invalid_request(j, "no machine"); + } + jsonrpc_check_no_params(j); + return jsonrpc_response_ok(j, session->handler->machine->get_runtime_config()); +} + +/// \brief JSONRPC handler for the machine.set_runtime_config method +/// \param j JSON request object +/// \param session HTTP session +/// \returns JSON response object +static json jsonrpc_machine_set_runtime_config_handler(const json &j, const std::shared_ptr &session) { + if (!session->handler->machine) { + return jsonrpc_response_invalid_request(j, "no machine"); + } + static const char *param_name[] = {"runtime_config"}; + auto args = parse_args(j, param_name); + session->handler->machine->set_runtime_config(std::get<0>(args)); + return jsonrpc_response_ok(j); +} + /// \brief JSONRPC handler for the machine.get_default_config method /// \param j JSON request object /// \param session HTTP session @@ -1361,10 +1425,13 @@ static json jsonrpc_dispatch_method(const json &j, const std::shared_ptrhandler->delay != 0) { + SLOG(trace) << session->handler->local_endpoint << " sleeping for " << session->handler->delay << "ms"; + std::this_thread::sleep_for(std::chrono::milliseconds(session->handler->delay)); + session->handler->delay = 0; + } json jri = jsonrpc_dispatch_method(ji, session); // Except for errors, do not add result of "notification" requests if (ji.contains("id")) { diff --git a/src/jsonrpc-virtual-machine.cpp b/src/jsonrpc-virtual-machine.cpp index 90c057f94..a63a5618b 100644 --- a/src/jsonrpc-virtual-machine.cpp +++ b/src/jsonrpc-virtual-machine.cpp @@ -19,7 +19,12 @@ #include #include #include +#include +#include #include +#include +#include +#include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" @@ -36,7 +41,6 @@ #include "base64.h" #include "htif.h" #include "json-util.h" -#include "jsonrpc-connection.h" #include "os.h" using namespace std::string_literals; @@ -79,11 +83,36 @@ static asio::ip::tcp::endpoint parse_endpoint(const std::string &address) { } } +class expiration { + beast::tcp_stream &m_stream; + +public: + expiration(beast::tcp_stream &stream, int64_t ms) : m_stream(stream) { + if (ms > 0) { + beast::get_lowest_layer(m_stream).expires_after(std::chrono::milliseconds(ms)); + } else { + beast::get_lowest_layer(m_stream).expires_never(); + } + } + expiration(const expiration &) = delete; + expiration &operator=(const expiration &) = delete; + expiration(expiration &&) = delete; + expiration &operator=(expiration &&) = delete; + // NOLINTNEXTLINE(bugprone-exception-escape) + ~expiration() { + beast::get_lowest_layer(m_stream).expires_never(); + } +}; + static std::string json_post(beast::tcp_stream &stream, const std::string &remote_address, const std::string &post_data, - bool keep_alive) { + int64_t ms, bool keep_alive) { // Determine remote endpoint from remote address const asio::ip::tcp::endpoint remote_endpoint = parse_endpoint(remote_address); + // Set expiration to ms milliseconds into the fiture, automatically clear it + // when function exits + const expiration exp(stream, ms); + // Close current stream socket when the remote endpoint is different if (stream.socket().is_open()) { beast::error_code ec; @@ -106,6 +135,8 @@ static std::string json_post(beast::tcp_stream &stream, const std::string &remot } else if (ec == asio::error::interrupted) { // Retry the operation during interrupts (SIGINT/SIGTERM), // otherwise we may leave dead zombies processes during fork requests. + } else if (ec == beast::error::timeout) { + throw std::runtime_error("jsonrpc error: timeout"); } else { // Unexpected error throw beast::system_error(ec); } @@ -152,6 +183,8 @@ static std::string json_post(beast::tcp_stream &stream, const std::string &remot } else if (ec == asio::error::interrupted) { // Retry the operation during interrupts (SIGINT/SIGTERM), // otherwise we may leave dead zombies processes during fork requests. + } else if (ec == beast::error::timeout) { + throw std::runtime_error("jsonrpc error: timeout"); } else { // Unexpected error throw beast::system_error(ec); } @@ -172,6 +205,8 @@ static std::string json_post(beast::tcp_stream &stream, const std::string &remot } else if (ec == asio::error::interrupted) { // Retry the operation during interrupts (SIGINT/SIGTERM), // otherwise we may leave dead zombies processes during fork requests. + } else if (ec == beast::error::timeout) { + throw std::runtime_error("jsonrpc error: timeout"); } else { // Unexpected error throw beast::system_error(ec); } @@ -179,7 +214,7 @@ static std::string json_post(beast::tcp_stream &stream, const std::string &remot http::response res = res_parser.release(); if (res.result() != http::status::ok) { - throw std::runtime_error("http error: reason "s + std::string(res.reason()) + " (code "s + + throw std::runtime_error("http error: reason "s + std::string(res.reason()) + " (code "s + std::to_string(res.result_int()) + ")"s); } @@ -205,13 +240,13 @@ static std::string json_post(beast::tcp_stream &stream, const std::string &remot template void jsonrpc_request(beast::tcp_stream &stream, const std::string &remote_address, const std::string &method, - const std::tuple &tp, R &result, bool keep_alive = true) { + const std::tuple &tp, R &result, int64_t ms, bool keep_alive = true) { auto request = jsonrpc_post_data(method, tp); std::string response_s; try { - response_s = json_post(stream, remote_address, request, keep_alive); + response_s = json_post(stream, remote_address, request, ms, keep_alive); } catch (std::exception &x) { - throw std::runtime_error("jsonrpc server error: post error ("s + x.what() + ")"s); + throw std::runtime_error("jsonrpc error: post error contacting "s + remote_address + " ("s + x.what() + ")"s); } json response; try { @@ -261,241 +296,298 @@ void jsonrpc_request(beast::tcp_stream &stream, const std::string &remote_addres namespace cartesi { -jsonrpc_connection::jsonrpc_connection(std::string address, bool detach_server) : m_detach_server(detach_server) { - m_address.push_back(std::move(address)); - // Install handler to ignore SIGPIPE lest we crash when a server closes a connection - os_disable_sigpipe(); +void jsonrpc_virtual_machine::shutdown_server(void) { + bool result = false; + jsonrpc_request(m_stream, m_address, "shutdown", std::tie(), result, m_timeout, false); } -jsonrpc_connection::~jsonrpc_connection() { - if (!m_detach_server) { - try { - // If configured to shutdown server, do it - shutdown_server(); - } catch (...) { // NOLINT(bugprone-empty-catch) - // We guard against exceptions here, which would only mean we failed to cleanup. - // We do not guarantee that we will cleanup. It's a best-effort thing. - } - } - // Gracefully close any established keep alive connection - if (m_stream.socket().is_open()) { - beast::error_code ec; - (void) m_stream.socket().shutdown(tcp::socket::shutdown_both, ec); - (void) m_stream.socket().close(ec); - } +void jsonrpc_virtual_machine::delay_next_request(uint64_t ms) const { + bool result = false; + jsonrpc_request(m_stream, m_address, "delay_next_request", std::tie(ms), result, m_timeout); } -beast::tcp_stream &jsonrpc_connection::get_stream(void) { - return m_stream; +void jsonrpc_virtual_machine::set_timeout(int64_t ms) { + m_timeout = ms; } -const beast::tcp_stream &jsonrpc_connection::get_stream(void) const { - return m_stream; +int64_t jsonrpc_virtual_machine::get_timeout(void) const { + return m_timeout; } -const std::string &jsonrpc_connection::get_remote_address(void) const { - if (is_shutdown()) { - throw std::out_of_range("remote server is shutdown"); - } - return m_address.back(); +void jsonrpc_virtual_machine::set_cleanup_call(cleanup_call call) { + m_call = call; } -const std::string &jsonrpc_connection::get_remote_parent_address(void) const { - if (!is_snapshot()) { - throw std::out_of_range("remote server is not forked"); - } - return m_address[0]; +auto jsonrpc_virtual_machine::get_cleanup_call(void) const -> cleanup_call { + return m_call; } -void jsonrpc_connection::snapshot(void) { - // If we are forked, discard the pending snapshot - if (is_snapshot()) { - commit(); - } - - // To create a snapshot, we fork a new server as the child and get its remote address - fork_result result{}; - jsonrpc_request(get_stream(), get_remote_address(), "fork", std::tie(), result, false); - m_address.push_back(std::move(result.address)); +const std::string &jsonrpc_virtual_machine::get_server_address(void) const { + return m_address; } -void jsonrpc_connection::commit() { - // If we are not forked, there is no pending snapshot to discard, therefore we are already committed - if (!is_snapshot()) { - return; - } +jsonrpc_virtual_machine::jsonrpc_virtual_machine(std::string address) : m_address(std::move(address)) { + // Install handler to ignore SIGPIPE lest we crash when a server closes a connection + os_disable_sigpipe(); +} - // To commit, we kill the parent server and replace its address with the child's +static boost::asio::ip::tcp::endpoint address_to_endpoint(const std::string &address) { try { - bool result = false; - jsonrpc_request(get_stream(), get_remote_parent_address(), "shutdown", std::tie(), result, false); - } catch (std::exception &e) { // NOLINT(bugprone-empty-catch) - // It's possible that the remote server was killed before the shutdown (e.g SIGTERM was sent), - // so we silently ignore errors here. - // If the server still up, the next rebind request will fail anyway with port already in use. + const auto pos = address.find_last_of(':'); + const std::string ip = address.substr(0, pos); + const int port = std::stoi(address.substr(pos + 1)); + if (port < 0 || port > 65535) { + throw std::runtime_error{"invalid port"}; + } + return {boost::asio::ip::make_address(ip), static_cast(port)}; + } catch (std::exception &e) { + throw std::runtime_error{"invalid endpoint address \"" + address + "\""}; } - - // Rebind the remote server to continue listening in the original port - std::string result; - jsonrpc_request(get_stream(), get_remote_address(), "rebind", std::tie(m_address[0]), result, false); - m_address.pop_back(); } -void jsonrpc_connection::rollback() { - // If we are not forked, there is no snapshot to rollback to - if (!is_snapshot()) { - throw std::out_of_range("remote server has no pending snapshot to rollback to"); +static std::string endpoint_to_string(const boost::asio::ip::tcp::endpoint &endpoint) { + std::ostringstream ss; + ss << endpoint; + return ss.str(); +} + +jsonrpc_virtual_machine::jsonrpc_virtual_machine(const std::string &address, fork_result &spawned) { + // this function first blocks SIGUSR1, SIGUSR2 and SIGALRM. + // then it double-forks. + // the grand-child sends the parent a SIGUSR2 and suicides if failed before execing jsonrpc-remote-cartesi-machine. + // otherwise, jsonrpc-remote-cartesi-machine itself sends the parent a SIGUSR1 to notify it is ready. + // the parent sets up to receive a SIGALRM after 15 seconds and then waits for SIGUSR1, SIGUSR2 or SIGALRM + // if it gets SIGALRM, the grand-child is unresponsive, so the parent kills it and the constructor fails. + // if it gets SIGUSR2, the grand-child failed before exec and suicided, so the constructor fails. + // if it gets SIGUSR1, jsonrpc-remote-cartesi-machine is ready and the constructor succeeds. + boost::asio::io_context ioc{1}; + // NOLINTNEXTLINE(clang-analyzer-optin.cplusplus.VirtualCall) + boost::asio::ip::tcp::acceptor a(ioc, address_to_endpoint(address)); + // already done by constructor + // a.open(endpoint.protocol()); + // a.set_option(asio::socket_base::reuse_address(true)); + // a.bind(endpoint); + // a.listen(asio::socket_base::max_listen_connections); + sigset_t mask{}; + sigset_t omask{}; + sigemptyset(&mask); // always returns 0 + sigaddset(&mask, SIGUSR1); // always returns 0 + sigaddset(&mask, SIGUSR2); // always returns 0 + sigaddset(&mask, SIGALRM); // always returns 0 + if (sigprocmask(SIG_BLOCK, &mask, &omask) < 0) { + // sigprocmask can only fail if we screwed up the values. this can't happen. + // being paranoid, if it *did* happen, we are trying to avoid a situation where + // our process gets killed when the grand-child or the alarm tries to signal us + // and the signals are not blocked + throw std::system_error{errno, std::generic_category(), "sigprocmask failed"}; } - - // To rollback, we kill the child and expose the parent server - bool result = false; - jsonrpc_request(get_stream(), get_remote_address(), "shutdown", std::tie(), result, false); - m_address.pop_back(); -} - -bool jsonrpc_connection::is_snapshot(void) const { - return m_address.size() > 1; -} - -void jsonrpc_connection::shutdown_server(void) { - bool result = false; - if (is_snapshot()) { - jsonrpc_request(get_stream(), get_remote_parent_address(), "shutdown", std::tie(), result, false); + bool restore_sigprocmask = true; + const char *bin = getenv("JSONRPC_REMOTE_CARTESI_MACHINE"); + if (!bin) { + bin = "jsonrpc-remote-cartesi-machine"; + } + auto ppid = getpid(); + bool restore_grand_child = false; + const int32_t grand_child = cartesi::os_double_fork_or_throw(false); + if (grand_child == 0) { // grand-child and double-fork() succeeded + sigprocmask(SIG_SETMASK, &omask, nullptr); + char sigusr1[256] = ""; + (void) snprintf(sigusr1, std::size(sigusr1), "--sigusr1=%d", ppid); + char server_fd[256] = ""; + (void) snprintf(server_fd, std::size(server_fd), "--server-fd=%d", a.native_handle()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + char *args[] = {const_cast(bin), server_fd, sigusr1, nullptr}; + if (execvp(bin, args) < 0) { + // here we failed to run jsonrpc-remote-cartesi-machine. nothing we can do. + kill(ppid, SIGUSR2); // notify parent as soon as possible that we failed. + exit(1); + }; + // code never reaches here + } else if (grand_child > 0) { // parent and double-fork() succeeded + restore_grand_child = true; // make sure grand-child is killed if we fail + m_address = endpoint_to_string(a.local_endpoint()); + a.close(); + struct itimerval ovalue {}; + bool restore_itimer = false; + try { + struct itimerval value {}; + memset(&value, 0, sizeof(value)); + value.it_interval.tv_sec = 0; + value.it_interval.tv_usec = 0; + value.it_value.tv_sec = 15; + value.it_value.tv_usec = 0; + if (setitimer(ITIMER_REAL, &value, &ovalue) < 0) { + // setitimer only fails if we screwed up with the values. this should not happen. + // being paranoid, if it *did* happen, and if the grand-child also failed to signal us, + // we might hang forever in the following call to sigwait. + // we prefer to give up instead of risking a deadlock. + throw std::system_error{errno, std::generic_category(), "setitimer failed"}; + } + restore_itimer = true; + int sig = 0; + if (auto ret = sigwait(&mask, &sig); ret != 0) { + throw std::system_error{ret, std::generic_category(), "sigwait failed"}; + } + if (sig == SIGALRM) { // grand-child didn't signal us before alarm + throw std::runtime_error{"grand-child process unresponsive"}; + } + if (sig == SIGUSR2) { // grand-child signaled us that it failed to exec + // grand-child will have exited on its own + restore_grand_child = false; + throw std::runtime_error{"failed to run '"s + bin + "'"s}; + } + // grand-child signaled us that everything is fine + assert(sig == SIGUSR1); + setitimer(ITIMER_REAL, &ovalue, nullptr); + restore_itimer = false; + sigprocmask(SIG_SETMASK, &omask, nullptr); + restore_sigprocmask = false; + spawned.pid = grand_child; + spawned.address = m_address; + // Install handler to ignore SIGPIPE lest we crash when a server closes a connection + os_disable_sigpipe(); + } catch (...) { + if (restore_sigprocmask) { + sigprocmask(SIG_SETMASK, &omask, nullptr); + } + if (restore_grand_child) { + kill(grand_child, SIGTERM); + } + if (restore_itimer) { + setitimer(ITIMER_REAL, &ovalue, nullptr); + } + throw; + } } - jsonrpc_request(get_stream(), get_remote_address(), "shutdown", std::tie(), result, false); - m_address.clear(); } -bool jsonrpc_connection::is_shutdown(void) const { - return m_address.empty(); +void jsonrpc_virtual_machine::do_load(const std::string &directory, const machine_runtime_config &runtime) { + bool result = false; + jsonrpc_request(m_stream, m_address, "machine.load", std::tie(directory, runtime), result, m_timeout); } -jsonrpc_virtual_machine::jsonrpc_virtual_machine(jsonrpc_connection_ptr con, bool detach_machine) : - m_connection(std::move(con)), - m_detach_machine(detach_machine) {} - -jsonrpc_virtual_machine::jsonrpc_virtual_machine(jsonrpc_connection_ptr con, bool detach_machine, - const std::string &directory, const machine_runtime_config &runtime) : - m_connection(std::move(con)), - m_detach_machine(detach_machine) { +bool jsonrpc_virtual_machine::do_is_empty(void) const { bool result = false; - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.machine.directory", - std::tie(directory, runtime), result); + jsonrpc_request(m_stream, m_address, "machine.is_empty", std::tie(), result, m_timeout); + return result; } -jsonrpc_virtual_machine::jsonrpc_virtual_machine(jsonrpc_connection_ptr con, bool detach_machine, - const machine_config &config, const machine_runtime_config &runtime) : - m_connection(std::move(con)), - m_detach_machine(detach_machine) { +i_virtual_machine *jsonrpc_virtual_machine::do_clone_empty(void) const { + auto fork_result = fork_server(); + auto *clone = new jsonrpc_virtual_machine(fork_result.address); + try { + if (!clone->is_empty()) { + clone->destroy(); + } + } catch (...) { + clone->shutdown_server(); + delete clone; + throw; + } + return clone; +}; + +void jsonrpc_virtual_machine::do_create(const machine_config &config, const machine_runtime_config &runtime) { bool result = false; - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.machine.config", - std::tie(config, runtime), result); + jsonrpc_request(m_stream, m_address, "machine.create", std::tie(config, runtime), result, m_timeout); } jsonrpc_virtual_machine::~jsonrpc_virtual_machine(void) { - if (!m_detach_machine) { + // If configured to destroy machine, do it + if (m_call == cleanup_call::destroy) { try { - // If configured to destroy machine, do it destroy(); } catch (...) { // NOLINT(bugprone-empty-catch) // We guard against exceptions here, which would only mean we failed to cleanup. // We do not guarantee that we will cleanup. It's a best-effort thing. } } + // If configured to shutdown server, do it + if (m_call == cleanup_call::shutdown) { + try { + shutdown_server(); + } catch (...) { // NOLINT(bugprone-empty-catch) + // We guard against exceptions here, which would only mean we failed to cleanup. + // We do not guarantee that we will cleanup. It's a best-effort thing. + } + } + // Gracefully close any established keep alive connection + if (m_stream.socket().is_open()) { + beast::error_code ec; + (void) m_stream.socket().shutdown(tcp::socket::shutdown_both, ec); + (void) m_stream.socket().close(ec); + } } machine_config jsonrpc_virtual_machine::do_get_initial_config(void) const { machine_config result; - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.get_initial_config", - std::tie(), result); + jsonrpc_request(m_stream, m_address, "machine.get_initial_config", std::tie(), result, m_timeout); return result; } -machine_config jsonrpc_virtual_machine::get_default_config(const jsonrpc_connection_ptr &con) { - machine_config result; - jsonrpc_request(con->get_stream(), con->get_remote_address(), "machine.get_default_config", std::tie(), result); +machine_runtime_config jsonrpc_virtual_machine::do_get_runtime_config(void) const { + machine_runtime_config result; + jsonrpc_request(m_stream, m_address, "machine.get_runtime_config", std::tie(), result, m_timeout); return result; } -semantic_version jsonrpc_connection::get_server_version(void) { - semantic_version result; - jsonrpc_request(get_stream(), get_remote_address(), "get_version", std::tie(), result); - return result; +void jsonrpc_virtual_machine::do_set_runtime_config(const machine_runtime_config &r) { + bool result = false; + jsonrpc_request(m_stream, m_address, "machine.set_runtime_config", std::tie(r), result, m_timeout); } -jsonrpc_connection_ptr jsonrpc_virtual_machine::get_connection(void) const { - return m_connection; +semantic_version jsonrpc_virtual_machine::get_server_version(void) const { + semantic_version result; + jsonrpc_request(m_stream, m_address, "get_version", std::tie(), result, m_timeout); + return result; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" -void jsonrpc_virtual_machine::verify_step_uarch(const jsonrpc_connection_ptr &con, const hash_type &root_hash_before, - const access_log &log, const hash_type &root_hash_after) { - bool result = false; - auto b64_root_hash_before = encode_base64(root_hash_before); - auto b64_root_hash_after = encode_base64(root_hash_after); - jsonrpc_request(con->get_stream(), con->get_remote_address(), "machine.verify_step_uarch", - std::tie(b64_root_hash_before, log, b64_root_hash_after), result); -} - -void jsonrpc_virtual_machine::verify_reset_uarch(const jsonrpc_connection_ptr &con, const hash_type &root_hash_before, - const access_log &log, const hash_type &root_hash_after) { - bool result = false; - auto b64_root_hash_before = encode_base64(root_hash_before); - auto b64_root_hash_after = encode_base64(root_hash_after); - jsonrpc_request(con->get_stream(), con->get_remote_address(), "machine.verify_reset_uarch", - std::tie(b64_root_hash_before, log, b64_root_hash_after), result); -} - interpreter_break_reason jsonrpc_virtual_machine::do_run(uint64_t mcycle_end) { interpreter_break_reason result = interpreter_break_reason::failed; - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.run", std::tie(mcycle_end), - result); + jsonrpc_request(m_stream, m_address, "machine.run", std::tie(mcycle_end), result, m_timeout); return result; } void jsonrpc_virtual_machine::do_store(const std::string &directory) const { bool result = false; - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.store", - std::tie(directory), result); + jsonrpc_request(m_stream, m_address, "machine.store", std::tie(directory), result, m_timeout); } uint64_t jsonrpc_virtual_machine::do_read_reg(reg r) const { uint64_t result = 0; - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.read_reg", std::tie(r), - result); + jsonrpc_request(m_stream, m_address, "machine.read_reg", std::tie(r), result, m_timeout); return result; } void jsonrpc_virtual_machine::do_write_reg(reg w, uint64_t val) { bool result = false; - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.write_reg", - std::tie(w, val), result); -} - -uint64_t jsonrpc_virtual_machine::get_reg_address(const jsonrpc_connection_ptr &con, reg r) { - uint64_t result = 0; - jsonrpc_request(con->get_stream(), con->get_remote_address(), "machine.get_reg_address", std::tie(r), result); - return result; + jsonrpc_request(m_stream, m_address, "machine.write_reg", std::tie(w, val), result, m_timeout); } -fork_result jsonrpc_connection::fork_server(void) { +auto jsonrpc_virtual_machine::fork_server(void) const -> fork_result { fork_result result{}; - jsonrpc_request(get_stream(), get_remote_address(), "fork", std::tie(), result, false); + jsonrpc_request(m_stream, m_address, "fork", std::tie(), result, m_timeout, false); return result; } -std::string jsonrpc_connection::rebind_server(const std::string &address) { +std::string jsonrpc_virtual_machine::rebind_server(const std::string &address) { std::string result; - jsonrpc_request(get_stream(), get_remote_address(), "rebind", std::tie(address), result, false); + jsonrpc_request(m_stream, m_address, "rebind", std::tie(address), result, m_timeout, false); + m_address = result; return result; } +void jsonrpc_virtual_machine::emancipate_server(void) const { + bool result = false; + jsonrpc_request(m_stream, m_address, "emancipate", std::tie(), result, m_timeout); +} + void jsonrpc_virtual_machine::do_read_memory(uint64_t address, unsigned char *data, uint64_t length) const { std::string result; - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.read_memory", - std::tie(address, length), result); + jsonrpc_request(m_stream, m_address, "machine.read_memory", std::tie(address, length), result, m_timeout); std::string bin = cartesi::decode_base64(result); if (bin.size() != length) { throw std::runtime_error("jsonrpc server error: invalid decoded base64 data length"); @@ -506,14 +598,12 @@ void jsonrpc_virtual_machine::do_read_memory(uint64_t address, unsigned char *da void jsonrpc_virtual_machine::do_write_memory(uint64_t address, const unsigned char *data, uint64_t length) { bool result = false; std::string b64 = cartesi::encode_base64(data, length); - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.write_memory", - std::tie(address, b64), result); + jsonrpc_request(m_stream, m_address, "machine.write_memory", std::tie(address, b64), result, m_timeout); } void jsonrpc_virtual_machine::do_read_virtual_memory(uint64_t address, unsigned char *data, uint64_t length) { std::string result; - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.read_virtual_memory", - std::tie(address, length), result); + jsonrpc_request(m_stream, m_address, "machine.read_virtual_memory", std::tie(address, length), result, m_timeout); std::string bin = cartesi::decode_base64(result); if (bin.size() != length) { throw std::runtime_error("jsonrpc server error: invalid decoded base64 data length"); @@ -524,27 +614,23 @@ void jsonrpc_virtual_machine::do_read_virtual_memory(uint64_t address, unsigned void jsonrpc_virtual_machine::do_write_virtual_memory(uint64_t address, const unsigned char *data, uint64_t length) { bool result = false; std::string b64 = cartesi::encode_base64(data, length); - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.write_virtual_memory", - std::tie(address, b64), result); + jsonrpc_request(m_stream, m_address, "machine.write_virtual_memory", std::tie(address, b64), result, m_timeout); } uint64_t jsonrpc_virtual_machine::do_translate_virtual_address(uint64_t vaddr) { uint64_t result = 0; - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.translate_virtual_address", - std::tie(vaddr), result); + jsonrpc_request(m_stream, m_address, "machine.translate_virtual_address", std::tie(vaddr), result, m_timeout); return result; } void jsonrpc_virtual_machine::do_reset_uarch(void) { bool result = false; - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.reset_uarch", std::tie(), - result); + jsonrpc_request(m_stream, m_address, "machine.reset_uarch", std::tie(), result, m_timeout); } access_log jsonrpc_virtual_machine::do_log_reset_uarch(const access_log::type &log_type) { not_default_constructible result; - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.log_reset_uarch", - std::tie(log_type), result); + jsonrpc_request(m_stream, m_address, "machine.log_reset_uarch", std::tie(log_type), result, m_timeout); if (!result.has_value()) { throw std::runtime_error("jsonrpc server error: missing result"); } @@ -552,14 +638,12 @@ access_log jsonrpc_virtual_machine::do_log_reset_uarch(const access_log::type &l } void jsonrpc_virtual_machine::do_get_root_hash(hash_type &hash) const { - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.get_root_hash", std::tie(), - hash); + jsonrpc_request(m_stream, m_address, "machine.get_root_hash", std::tie(), hash, m_timeout); } machine_merkle_tree::proof_type jsonrpc_virtual_machine::do_get_proof(uint64_t address, int log2_size) const { not_default_constructible result; - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.get_proof", - std::tie(address, log2_size), result); + jsonrpc_request(m_stream, m_address, "machine.get_proof", std::tie(address, log2_size), result, m_timeout); if (!result.has_value()) { throw std::runtime_error("jsonrpc server error: missing result"); } @@ -568,14 +652,12 @@ machine_merkle_tree::proof_type jsonrpc_virtual_machine::do_get_proof(uint64_t a void jsonrpc_virtual_machine::do_replace_memory_range(const memory_range_config &new_range) { bool result = false; - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.replace_memory_range", - std::tie(new_range), result); + jsonrpc_request(m_stream, m_address, "machine.replace_memory_range", std::tie(new_range), result, m_timeout); } access_log jsonrpc_virtual_machine::do_log_step_uarch(const access_log::type &log_type) { not_default_constructible result; - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.log_step_uarch", - std::tie(log_type), result); + jsonrpc_request(m_stream, m_address, "machine.log_step_uarch", std::tie(log_type), result, m_timeout); if (!result.has_value()) { throw std::runtime_error("jsonrpc server error: missing result"); } @@ -584,85 +666,99 @@ access_log jsonrpc_virtual_machine::do_log_step_uarch(const access_log::type &lo void jsonrpc_virtual_machine::do_destroy() { bool result = false; - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.destroy", std::tie(), - result, false); + jsonrpc_request(m_stream, m_address, "machine.destroy", std::tie(), result, m_timeout); } bool jsonrpc_virtual_machine::do_verify_dirty_page_maps(void) const { bool result = false; - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.verify_dirty_page_maps", - std::tie(), result); + jsonrpc_request(m_stream, m_address, "machine.verify_dirty_page_maps", std::tie(), result, m_timeout); return result; } uint64_t jsonrpc_virtual_machine::do_read_word(uint64_t address) const { uint64_t result = 0; - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.read_word", - std::tie(address), result); + jsonrpc_request(m_stream, m_address, "machine.read_word", std::tie(address), result, m_timeout); return result; } bool jsonrpc_virtual_machine::do_verify_merkle_tree(void) const { bool result = false; - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.verify_merkle_tree", - std::tie(), result); + jsonrpc_request(m_stream, m_address, "machine.verify_merkle_tree", std::tie(), result, m_timeout); return result; } -void jsonrpc_virtual_machine::do_snapshot(void) { - m_connection->snapshot(); -} - -void jsonrpc_virtual_machine::do_commit(void) { - m_connection->commit(); -} - -void jsonrpc_virtual_machine::do_rollback(void) { - m_connection->rollback(); -} - uarch_interpreter_break_reason jsonrpc_virtual_machine::do_run_uarch(uint64_t uarch_cycle_end) { uarch_interpreter_break_reason result = uarch_interpreter_break_reason::reached_target_cycle; - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.run_uarch", - std::tie(uarch_cycle_end), result); + jsonrpc_request(m_stream, m_address, "machine.run_uarch", std::tie(uarch_cycle_end), result, m_timeout); return result; } machine_memory_range_descrs jsonrpc_virtual_machine::do_get_memory_ranges(void) const { machine_memory_range_descrs result; - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.get_memory_ranges", - std::tie(), result); + jsonrpc_request(m_stream, m_address, "machine.get_memory_ranges", std::tie(), result, m_timeout); return result; } void jsonrpc_virtual_machine::do_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length) { bool result = false; std::string b64 = cartesi::encode_base64(data, length); - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.send_cmio_response", - std::tie(reason, b64), result); + jsonrpc_request(m_stream, m_address, "machine.send_cmio_response", std::tie(reason, b64), result, m_timeout); } access_log jsonrpc_virtual_machine::do_log_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length, const access_log::type &log_type) { not_default_constructible result; std::string b64 = cartesi::encode_base64(data, length); - jsonrpc_request(m_connection->get_stream(), m_connection->get_remote_address(), "machine.log_send_cmio_response", - std::tie(reason, b64, log_type), result); + jsonrpc_request(m_stream, m_address, "machine.log_send_cmio_response", std::tie(reason, b64, log_type), result, + m_timeout); if (!result.has_value()) { throw std::runtime_error("jsonrpc server error: missing result"); } return std::move(result).value(); } -void jsonrpc_virtual_machine::verify_send_cmio_response(const jsonrpc_connection_ptr &con, uint16_t reason, - const unsigned char *data, uint64_t length, const hash_type &root_hash_before, const access_log &log, - const hash_type &root_hash_after) { +uint64_t jsonrpc_virtual_machine::do_get_reg_address(reg r) const { + uint64_t result = 0; + jsonrpc_request(m_stream, m_address, "machine.get_reg_address", std::tie(r), result, m_timeout); + return result; +} + +machine_config jsonrpc_virtual_machine::do_get_default_config(void) const { + machine_config result; + jsonrpc_request(m_stream, m_address, "machine.get_default_config", std::tie(), result, m_timeout); + return result; +} + +void jsonrpc_virtual_machine::do_verify_step_uarch(const hash_type &root_hash_before, const access_log &log, + const hash_type &root_hash_after) const { + bool result = false; + auto b64_root_hash_before = encode_base64(root_hash_before); + auto b64_root_hash_after = encode_base64(root_hash_after); + jsonrpc_request(m_stream, m_address, "machine.verify_step_uarch", + std::tie(b64_root_hash_before, log, b64_root_hash_after), result, m_timeout); +} + +void jsonrpc_virtual_machine::do_verify_reset_uarch(const hash_type &root_hash_before, const access_log &log, + const hash_type &root_hash_after) const { + bool result = false; + auto b64_root_hash_before = encode_base64(root_hash_before); + auto b64_root_hash_after = encode_base64(root_hash_after); + jsonrpc_request(m_stream, m_address, "machine.verify_reset_uarch", + std::tie(b64_root_hash_before, log, b64_root_hash_after), result, m_timeout); +} + +void jsonrpc_virtual_machine::do_verify_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length, + const hash_type &root_hash_before, const access_log &log, const hash_type &root_hash_after) const { bool result = false; std::string b64_data = cartesi::encode_base64(data, length); auto b64_root_hash_before = encode_base64(root_hash_before); auto b64_root_hash_after = encode_base64(root_hash_after); - jsonrpc_request(con->get_stream(), con->get_remote_address(), "machine.verify_send_cmio_response", - std::tie(reason, b64_data, b64_root_hash_before, log, b64_root_hash_after), result); + jsonrpc_request(m_stream, m_address, "machine.verify_send_cmio_response", + std::tie(reason, b64_data, b64_root_hash_before, log, b64_root_hash_after), result, m_timeout); +} + +bool jsonrpc_virtual_machine::do_is_jsonrpc_virtual_machine(void) const { + return true; } #pragma GCC diagnostic pop diff --git a/src/jsonrpc-virtual-machine.h b/src/jsonrpc-virtual-machine.h index 1d84eafe8..34e4aceb4 100644 --- a/src/jsonrpc-virtual-machine.h +++ b/src/jsonrpc-virtual-machine.h @@ -21,50 +21,79 @@ #include #include +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#include "asio-config.h" // must be included before any ASIO header +#include +#include +#include +#pragma GCC diagnostic pop + #include "i-virtual-machine.h" +#include "jsonrpc-fork-result.h" +#include "semantic-version.h" namespace cartesi { -/// \class jsonrpc_connection -/// \brief Connection to the server -class jsonrpc_connection; - -using jsonrpc_connection_ptr = std::shared_ptr; - /// \class jsonrpc_virtual_machine /// \brief JSONRPC implementation of the i_virtual_machine interface class jsonrpc_virtual_machine final : public i_virtual_machine { public: - jsonrpc_virtual_machine(jsonrpc_connection_ptr con, bool detach_machine); - jsonrpc_virtual_machine(jsonrpc_connection_ptr con, bool detach_machine, const std::string &dir, - const machine_runtime_config &r = {}); - jsonrpc_virtual_machine(jsonrpc_connection_ptr con, bool detach_machine, const machine_config &c, - const machine_runtime_config &r = {}); + enum class cleanup_call { nothing, destroy, shutdown }; + + /// \brief Constructor that connects to existing server + jsonrpc_virtual_machine(std::string address); + + /// \brief Constructor that spawns a new server + jsonrpc_virtual_machine(const std::string &address, fork_result &spawned); - jsonrpc_virtual_machine(const jsonrpc_virtual_machine &other) = delete; - jsonrpc_virtual_machine(jsonrpc_virtual_machine &&other) noexcept = delete; - jsonrpc_virtual_machine &operator=(const jsonrpc_virtual_machine &other) = delete; - jsonrpc_virtual_machine &operator=(jsonrpc_virtual_machine &&other) noexcept = delete; ~jsonrpc_virtual_machine(); - jsonrpc_connection_ptr get_connection(void) const; + /// \brief Asks remote server to shutdown + void shutdown_server(void); + + /// \brief Forks remote server + fork_result fork_server(void) const; - static machine_config get_default_config(const jsonrpc_connection_ptr &con); + /// \brief Ask remote server to change the address from which it accepts connections + std::string rebind_server(const std::string &address); - static void verify_step_uarch(const jsonrpc_connection_ptr &con, const hash_type &root_hash_before, - const access_log &log, const hash_type &root_hash_after); + /// \brief Obtains the remote server version + semantic_version get_server_version(void) const; - static void verify_reset_uarch(const jsonrpc_connection_ptr &con, const hash_type &root_hash_before, - const access_log &log, const hash_type &root_hash_after); + /// \brief Breaks server out of parent program group + void emancipate_server(void) const; - static void verify_send_cmio_response(const jsonrpc_connection_ptr &con, uint16_t reason, const unsigned char *data, - uint64_t length, const hash_type &root_hash_before, const access_log &log, const hash_type &root_hash_after); + /// \brief Sets timeout for communicating with server + void set_timeout(int64_t ms); - static uint64_t get_reg_address(const jsonrpc_connection_ptr &con, reg r); + /// \brief Asks server to delay next request by a given amount of time + void delay_next_request(uint64_t ms) const; + + /// \brief Gets timeout for communicating with server + int64_t get_timeout(void) const; + + /// \brief Sets timeout for communicating with server + void set_cleanup_call(cleanup_call call); + + /// \brief Sets timeout for communicating with server + cleanup_call get_cleanup_call(void) const; + + /// \brief Returns address of remote remote server + const std::string &get_server_address(void) const; + + // no copies or assignments + jsonrpc_virtual_machine(const jsonrpc_virtual_machine &other) = delete; + jsonrpc_virtual_machine &operator=(const jsonrpc_virtual_machine &other) = delete; + jsonrpc_virtual_machine(jsonrpc_virtual_machine &&other) noexcept = delete; + jsonrpc_virtual_machine &operator=(jsonrpc_virtual_machine &&other) noexcept = delete; private: + i_virtual_machine *do_clone_empty(void) const override; + bool do_is_empty(void) const override; machine_config do_get_initial_config(void) const override; - + void do_create(const machine_config &config, const machine_runtime_config &runtime) override; + void do_load(const std::string &directory, const machine_runtime_config &runtime) override; interpreter_break_reason do_run(uint64_t mcycle_end) override; void do_store(const std::string &dir) const override; uint64_t do_read_reg(reg r) const override; @@ -80,10 +109,9 @@ class jsonrpc_virtual_machine final : public i_virtual_machine { machine_merkle_tree::proof_type do_get_proof(uint64_t address, int log2_size) const override; void do_replace_memory_range(const memory_range_config &new_range) override; access_log do_log_step_uarch(const access_log::type &log_type) override; + machine_runtime_config do_get_runtime_config(void) const override; + void do_set_runtime_config(const machine_runtime_config &r) override; void do_destroy() override; - void do_snapshot() override; - void do_commit() override; - void do_rollback() override; bool do_verify_dirty_page_maps(void) const override; uint64_t do_read_word(uint64_t address) const override; bool do_verify_merkle_tree(void) const override; @@ -92,8 +120,21 @@ class jsonrpc_virtual_machine final : public i_virtual_machine { void do_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length) override; access_log do_log_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length, const access_log::type &log_type) override; - jsonrpc_connection_ptr m_connection; - bool m_detach_machine; + uint64_t do_get_reg_address(reg r) const override; + machine_config do_get_default_config(void) const override; + void do_verify_step_uarch(const hash_type &root_hash_before, const access_log &log, + const hash_type &root_hash_after) const override; + void do_verify_reset_uarch(const hash_type &root_hash_before, const access_log &log, + const hash_type &root_hash_after) const override; + void do_verify_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length, + const hash_type &root_hash_before, const access_log &log, const hash_type &root_hash_after) const override; + virtual bool do_is_jsonrpc_virtual_machine(void) const override; + + mutable boost::asio::io_context m_ioc{1}; // The io_context is required for all I/O + mutable boost::beast::tcp_stream m_stream{m_ioc}; // TCP stream for keep alive connections + cleanup_call m_call{cleanup_call::nothing}; + std::string m_address{}; + int64_t m_timeout = -1; }; } // namespace cartesi diff --git a/src/machine-c-api.cpp b/src/machine-c-api.cpp index dc3701ce3..8bc1d6c08 100644 --- a/src/machine-c-api.cpp +++ b/src/machine-c-api.cpp @@ -149,32 +149,55 @@ cartesi::machine_merkle_tree::hash_type convert_from_c(const cm_hash *c_hash) { // The C API implementation // ---------------------------------------------- -cm_error cm_create(const char *config, const char *runtime_config, cm_machine **new_machine) try { +cm_error cm_new(const cm_machine *m, cm_machine **new_m) try { + if (new_m == nullptr) { + throw std::invalid_argument("invalid new machine output"); + } + if (m == nullptr) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + *new_m = reinterpret_cast(new cartesi::virtual_machine()); + } else { + const auto *cpp_m = convert_from_c(m); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + *new_m = reinterpret_cast(cpp_m->clone_empty()); + } + return cm_result_success(); +} catch (...) { + if (new_m) { + *new_m = nullptr; + } + return cm_result_failure(); +} + +cm_error cm_is_empty(const cm_machine *m, bool *yes) try { + if (yes == nullptr) { + throw std::invalid_argument("invalid yes output"); + } + const auto *cpp_m = convert_from_c(m); + *yes = cpp_m->is_empty(); + return cm_result_success(); +} catch (...) { + return cm_result_failure(); +} + +cm_error cm_create(cm_machine *m, const char *config, const char *runtime_config) try { + auto *cpp_m = convert_from_c(m); if (config == nullptr) { throw std::invalid_argument("invalid machine configuration"); } - if (new_machine == nullptr) { - throw std::invalid_argument("invalid new machine output"); - } const auto c = cartesi::from_json(config); cartesi::machine_runtime_config r; if (runtime_config) { r = cartesi::from_json(runtime_config); } - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - *new_machine = reinterpret_cast(new cartesi::virtual_machine(c, r)); + cpp_m->create(c, r); return cm_result_success(); } catch (...) { - if (new_machine) { - *new_machine = nullptr; - } return cm_result_failure(); } -cm_error cm_load(const char *dir, const char *runtime_config, cm_machine **new_machine) try { - if (new_machine == nullptr) { - throw std::invalid_argument("invalid new machine output"); - } +cm_error cm_load(cm_machine *m, const char *dir, const char *runtime_config) try { + auto *cpp_m = convert_from_c(m); if (dir == nullptr) { throw std::invalid_argument("invalid dir"); } @@ -182,30 +205,52 @@ cm_error cm_load(const char *dir, const char *runtime_config, cm_machine **new_m if (runtime_config) { r = cartesi::from_json(runtime_config); } - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - *new_machine = reinterpret_cast(new cartesi::virtual_machine(dir, r)); + cpp_m->load(dir, r); return cm_result_success(); } catch (...) { - if (new_machine) { - *new_machine = nullptr; - } return cm_result_failure(); } +cm_error cm_load_new(const char *dir, const char *runtime_config, cm_machine **new_m) { + auto err = cm_new(nullptr, new_m); + if (err != 0) { + return err; + } + err = cm_load(*new_m, dir, runtime_config); + if (err != 0) { + cm_delete(*new_m); + *new_m = nullptr; + } + return err; +} + +cm_error cm_create_new(const char *config, const char *runtime_config, cm_machine **new_m) { + auto err = cm_new(nullptr, new_m); + if (err != 0) { + return err; + } + err = cm_create(*new_m, config, runtime_config); + if (err != 0) { + cm_delete(*new_m); + *new_m = nullptr; + } + return err; +} + cm_error cm_store(const cm_machine *m, const char *dir) try { if (dir == nullptr) { throw std::invalid_argument("invalid dir"); } - const auto *cpp_machine = convert_from_c(m); - cpp_machine->store(dir); + const auto *cpp_m = convert_from_c(m); + cpp_m->store(dir); return cm_result_success(); } catch (...) { return cm_result_failure(); } cm_error cm_run(cm_machine *m, uint64_t mcycle_end, cm_break_reason *break_reason) try { - auto *cpp_machine = convert_from_c(m); - const auto status = cpp_machine->run(mcycle_end); + auto *cpp_m = convert_from_c(m); + const auto status = cpp_m->run(mcycle_end); if (break_reason) { *break_reason = static_cast(status); } @@ -218,8 +263,8 @@ cm_error cm_run(cm_machine *m, uint64_t mcycle_end, cm_break_reason *break_reaso } cm_error cm_read_uarch_halt_flag(const cm_machine *m, bool *val) try { - const auto *cpp_machine = convert_from_c(m); - *val = static_cast(cpp_machine->read_reg(cartesi::machine::reg::uarch_halt_flag)); + const auto *cpp_m = convert_from_c(m); + *val = static_cast(cpp_m->read_reg(cartesi::machine::reg::uarch_halt_flag)); return cm_result_success(); } catch (...) { if (val) { @@ -229,16 +274,16 @@ cm_error cm_read_uarch_halt_flag(const cm_machine *m, bool *val) try { } cm_error cm_set_uarch_halt_flag(cm_machine *m) try { - auto *cpp_machine = convert_from_c(m); - cpp_machine->write_reg(cartesi::machine::reg::uarch_halt_flag, 1); + auto *cpp_m = convert_from_c(m); + cpp_m->write_reg(cartesi::machine::reg::uarch_halt_flag, 1); return cm_result_success(); } catch (...) { return cm_result_failure(); } cm_error cm_reset_uarch(cm_machine *m) try { - auto *cpp_machine = convert_from_c(m); - cpp_machine->reset_uarch(); + auto *cpp_m = convert_from_c(m); + cpp_m->reset_uarch(); return cm_result_success(); } catch (...) { return cm_result_failure(); @@ -248,9 +293,9 @@ cm_error cm_log_reset_uarch(cm_machine *m, int32_t log_type, const char **log) t if (log == nullptr) { throw std::invalid_argument("invalid access log output"); } - auto *cpp_machine = convert_from_c(m); + auto *cpp_m = convert_from_c(m); cartesi::access_log::type cpp_log_type(log_type); - cartesi::access_log cpp_log = cpp_machine->log_reset_uarch(cpp_log_type); + cartesi::access_log cpp_log = cpp_m->log_reset_uarch(cpp_log_type); *log = cm_set_temp_string(cartesi::to_json(cpp_log).dump()); return cm_result_success(); } catch (...) { @@ -261,8 +306,8 @@ cm_error cm_log_reset_uarch(cm_machine *m, int32_t log_type, const char **log) t } cm_error cm_run_uarch(cm_machine *m, uint64_t uarch_cycle_end, cm_uarch_break_reason *uarch_break_reason) try { - auto *cpp_machine = convert_from_c(m); - const auto status = cpp_machine->run_uarch(uarch_cycle_end); + auto *cpp_m = convert_from_c(m); + const auto status = cpp_m->run_uarch(uarch_cycle_end); if (uarch_break_reason) { *uarch_break_reason = static_cast(status); } @@ -278,9 +323,9 @@ cm_error cm_log_step_uarch(cm_machine *m, int32_t log_type, const char **log) tr if (log == nullptr) { throw std::invalid_argument("invalid access log output"); } - auto *cpp_machine = convert_from_c(m); + auto *cpp_m = convert_from_c(m); cartesi::access_log::type cpp_log_type(log_type); - cartesi::access_log cpp_log = cpp_machine->log_step_uarch(cpp_log_type); + cartesi::access_log cpp_log = cpp_m->log_step_uarch(cpp_log_type); *log = cm_set_temp_string(cartesi::to_json(cpp_log).dump()); return cm_result_success(); } catch (...) { @@ -290,7 +335,8 @@ cm_error cm_log_step_uarch(cm_machine *m, int32_t log_type, const char **log) tr return cm_result_failure(); } -cm_error cm_verify_step_uarch(const cm_hash *root_hash_before, const char *log, const cm_hash *root_hash_after) try { +cm_error cm_verify_step_uarch(const cm_machine *m, const cm_hash *root_hash_before, const char *log, + const cm_hash *root_hash_after) try { if (log == nullptr) { throw std::invalid_argument("invalid access log"); } @@ -298,13 +344,19 @@ cm_error cm_verify_step_uarch(const cm_hash *root_hash_before, const char *log, cartesi::from_json>(log).value(); const cartesi::machine::hash_type cpp_root_hash_before = convert_from_c(root_hash_before); const cartesi::machine::hash_type cpp_root_hash_after = convert_from_c(root_hash_after); - cartesi::machine::verify_step_uarch(cpp_root_hash_before, cpp_log, cpp_root_hash_after); + if (m != nullptr) { + const auto *cpp_m = convert_from_c(m); + cpp_m->verify_step_uarch(cpp_root_hash_before, cpp_log, cpp_root_hash_after); + } else { + cartesi::machine::verify_step_uarch(cpp_root_hash_before, cpp_log, cpp_root_hash_after); + } return cm_result_success(); } catch (...) { return cm_result_failure(); } -cm_error cm_verify_reset_uarch(const cm_hash *root_hash_before, const char *log, const cm_hash *root_hash_after) try { +cm_error cm_verify_reset_uarch(const cm_machine *m, const cm_hash *root_hash_before, const char *log, + const cm_hash *root_hash_after) try { if (log == nullptr) { throw std::invalid_argument("invalid access log"); } @@ -312,7 +364,12 @@ cm_error cm_verify_reset_uarch(const cm_hash *root_hash_before, const char *log, cartesi::from_json>(log).value(); const cartesi::machine::hash_type cpp_root_hash_before = convert_from_c(root_hash_before); const cartesi::machine::hash_type cpp_root_hash_after = convert_from_c(root_hash_after); - cartesi::machine::verify_reset_uarch(cpp_root_hash_before, cpp_log, cpp_root_hash_after); + if (m != nullptr) { + const auto *cpp_m = convert_from_c(m); + cpp_m->verify_reset_uarch(cpp_root_hash_before, cpp_log, cpp_root_hash_after); + } else { + cartesi::machine::verify_reset_uarch(cpp_root_hash_before, cpp_log, cpp_root_hash_after); + } return cm_result_success(); } catch (...) { return cm_result_failure(); @@ -322,8 +379,8 @@ cm_error cm_get_proof(const cm_machine *m, uint64_t address, int32_t log2_size, if (proof == nullptr) { throw std::invalid_argument("invalid proof output"); } - const auto *cpp_machine = convert_from_c(m); - const cartesi::machine_merkle_tree::proof_type cpp_proof = cpp_machine->get_proof(address, log2_size); + const auto *cpp_m = convert_from_c(m); + const cartesi::machine_merkle_tree::proof_type cpp_proof = cpp_m->get_proof(address, log2_size); *proof = cm_set_temp_string(cartesi::to_json(cpp_proof).dump()); return cm_result_success(); } catch (...) { @@ -337,9 +394,9 @@ cm_error cm_get_root_hash(const cm_machine *m, cm_hash *hash) try { if (hash == nullptr) { throw std::invalid_argument("invalid hash output"); } - const auto *cpp_machine = convert_from_c(m); + const auto *cpp_m = convert_from_c(m); cartesi::machine_merkle_tree::hash_type cpp_hash; - cpp_machine->get_root_hash(cpp_hash); + cpp_m->get_root_hash(cpp_hash); memcpy(hash, static_cast(cpp_hash.data()), sizeof(cm_hash)); return cm_result_success(); } catch (...) { @@ -350,8 +407,8 @@ cm_error cm_verify_merkle_tree(cm_machine *m, bool *result) try { if (result == nullptr) { throw std::invalid_argument("invalid result output"); } - auto *cpp_machine = convert_from_c(m); - *result = cpp_machine->verify_merkle_tree(); + auto *cpp_m = convert_from_c(m); + *result = cpp_m->verify_merkle_tree(); return cm_result_success(); } catch (...) { if (result) { @@ -364,9 +421,9 @@ cm_error cm_read_reg(const cm_machine *m, cm_reg reg, uint64_t *val) try { if (val == nullptr) { throw std::invalid_argument("invalid val output"); } - const auto *cpp_machine = convert_from_c(m); + const auto *cpp_m = convert_from_c(m); auto cpp_reg = static_cast(reg); - *val = cpp_machine->read_reg(cpp_reg); + *val = cpp_m->read_reg(cpp_reg); return cm_result_success(); } catch (...) { if (val) { @@ -376,20 +433,25 @@ cm_error cm_read_reg(const cm_machine *m, cm_reg reg, uint64_t *val) try { } cm_error cm_write_reg(cm_machine *m, cm_reg reg, uint64_t val) try { - auto *cpp_machine = convert_from_c(m); + auto *cpp_m = convert_from_c(m); auto cpp_reg = static_cast(reg); - cpp_machine->write_reg(cpp_reg, val); + cpp_m->write_reg(cpp_reg, val); return cm_result_success(); } catch (...) { return cm_result_failure(); } -cm_error cm_get_reg_address(cm_reg reg, uint64_t *val) try { +cm_error cm_get_reg_address(const cm_machine *m, cm_reg reg, uint64_t *val) try { if (val == nullptr) { throw std::invalid_argument("invalid val output"); } auto cpp_reg = static_cast(reg); - *val = cartesi::machine::get_reg_address(cpp_reg); + if (m != nullptr) { + const auto *cpp_m = convert_from_c(m); + *val = cpp_m->get_reg_address(cpp_reg); + } else { + *val = cartesi::machine::get_reg_address(cpp_reg); + } return cm_result_success(); } catch (...) { if (val) { @@ -402,8 +464,8 @@ cm_error cm_read_word(const cm_machine *m, uint64_t address, uint64_t *val) try if (val == nullptr) { throw std::invalid_argument("invalid word output"); } - const auto *cpp_machine = convert_from_c(m); - *val = cpp_machine->read_word(address); + const auto *cpp_m = convert_from_c(m); + *val = cpp_m->read_word(address); return cm_result_success(); } catch (...) { if (val) { @@ -413,40 +475,40 @@ cm_error cm_read_word(const cm_machine *m, uint64_t address, uint64_t *val) try } cm_error cm_read_memory(const cm_machine *m, uint64_t address, uint8_t *data, uint64_t length) try { - const auto *cpp_machine = convert_from_c(m); - cpp_machine->read_memory(address, data, length); + const auto *cpp_m = convert_from_c(m); + cpp_m->read_memory(address, data, length); return cm_result_success(); } catch (...) { return cm_result_failure(); } cm_error cm_write_memory(cm_machine *m, uint64_t address, const uint8_t *data, uint64_t length) try { - auto *cpp_machine = convert_from_c(m); - cpp_machine->write_memory(address, data, length); + auto *cpp_m = convert_from_c(m); + cpp_m->write_memory(address, data, length); return cm_result_success(); } catch (...) { return cm_result_failure(); } cm_error cm_read_virtual_memory(cm_machine *m, uint64_t address, uint8_t *data, uint64_t length) try { - auto *cpp_machine = convert_from_c(m); - cpp_machine->read_virtual_memory(address, data, length); + auto *cpp_m = convert_from_c(m); + cpp_m->read_virtual_memory(address, data, length); return cm_result_success(); } catch (...) { return cm_result_failure(); } cm_error cm_write_virtual_memory(cm_machine *m, uint64_t address, const uint8_t *data, uint64_t length) try { - auto *cpp_machine = convert_from_c(m); - cpp_machine->write_virtual_memory(address, data, length); + auto *cpp_m = convert_from_c(m); + cpp_m->write_virtual_memory(address, data, length); return cm_result_success(); } catch (...) { return cm_result_failure(); } cm_error cm_translate_virtual_address(cm_machine *m, uint64_t vaddr, uint64_t *paddr) try { - auto *cpp_machine = convert_from_c(m); - *paddr = cpp_machine->translate_virtual_address(vaddr); + auto *cpp_m = convert_from_c(m); + *paddr = cpp_m->translate_virtual_address(vaddr); return cm_result_success(); } catch (...) { if (paddr) { @@ -459,8 +521,8 @@ cm_error cm_read_mcycle(const cm_machine *m, uint64_t *val) try { if (val == nullptr) { throw std::invalid_argument("invalid val output"); } - const auto *cpp_machine = convert_from_c(m); - *val = cpp_machine->read_reg(cartesi::machine::reg::mcycle); + const auto *cpp_m = convert_from_c(m); + *val = cpp_m->read_reg(cartesi::machine::reg::mcycle); return cm_result_success(); } catch (...) { if (val) { @@ -473,8 +535,8 @@ cm_error cm_read_uarch_cycle(const cm_machine *m, uint64_t *val) try { if (val == nullptr) { throw std::invalid_argument("invalid val output"); } - const auto *cpp_machine = convert_from_c(m); - *val = cpp_machine->read_reg(cartesi::machine::reg::uarch_cycle); + const auto *cpp_m = convert_from_c(m); + *val = cpp_m->read_reg(cartesi::machine::reg::uarch_cycle); return cm_result_success(); } catch (...) { if (val) { @@ -487,8 +549,8 @@ cm_error cm_read_iflags_Y(const cm_machine *m, bool *val) try { if (val == nullptr) { throw std::invalid_argument("invalid val output"); } - const auto *cpp_machine = convert_from_c(m); - *val = static_cast(cpp_machine->read_reg(cartesi::machine::reg::iflags_y)); + const auto *cpp_m = convert_from_c(m); + *val = static_cast(cpp_m->read_reg(cartesi::machine::reg::iflags_y)); return cm_result_success(); } catch (...) { if (val) { @@ -498,16 +560,16 @@ cm_error cm_read_iflags_Y(const cm_machine *m, bool *val) try { } cm_error cm_reset_iflags_Y(cm_machine *m) try { - auto *cpp_machine = convert_from_c(m); - cpp_machine->write_reg(cartesi::machine::reg::iflags_y, 0); + auto *cpp_m = convert_from_c(m); + cpp_m->write_reg(cartesi::machine::reg::iflags_y, 0); return cm_result_success(); } catch (...) { return cm_result_failure(); } cm_error cm_set_iflags_Y(cm_machine *m) try { - auto *cpp_machine = convert_from_c(m); - cpp_machine->write_reg(cartesi::machine::reg::iflags_y, 1); + auto *cpp_m = convert_from_c(m); + cpp_m->write_reg(cartesi::machine::reg::iflags_y, 1); return cm_result_success(); } catch (...) { return cm_result_failure(); @@ -517,8 +579,8 @@ cm_error cm_read_iflags_X(const cm_machine *m, bool *val) try { if (val == nullptr) { throw std::invalid_argument("invalid val output"); } - const auto *cpp_machine = convert_from_c(m); - *val = static_cast(cpp_machine->read_reg(cartesi::machine::reg::iflags_x)); + const auto *cpp_m = convert_from_c(m); + *val = static_cast(cpp_m->read_reg(cartesi::machine::reg::iflags_x)); return cm_result_success(); } catch (...) { if (val) { @@ -531,8 +593,8 @@ cm_error cm_read_iflags_H(const cm_machine *m, bool *val) try { if (val == nullptr) { throw std::invalid_argument("invalid val output"); } - const auto *cpp_machine = convert_from_c(m); - *val = static_cast(cpp_machine->read_reg(cartesi::machine::reg::iflags_h)); + const auto *cpp_m = convert_from_c(m); + *val = static_cast(cpp_m->read_reg(cartesi::machine::reg::iflags_h)); return cm_result_success(); } catch (...) { if (val) { @@ -545,8 +607,8 @@ cm_error cm_verify_dirty_page_maps(cm_machine *m, bool *result) try { if (result == nullptr) { throw std::invalid_argument("invalid result output"); } - auto *cpp_machine = convert_from_c(m); - *result = cpp_machine->verify_dirty_page_maps(); + auto *cpp_m = convert_from_c(m); + *result = cpp_m->verify_dirty_page_maps(); return cm_result_success(); } catch (...) { if (result) { @@ -559,8 +621,8 @@ cm_error cm_get_initial_config(const cm_machine *m, const char **config) try { if (config == nullptr) { throw std::invalid_argument("invalid config output"); } - const auto *cpp_machine = convert_from_c(m); - const cartesi::machine_config cpp_config = cpp_machine->get_initial_config(); + const auto *cpp_m = convert_from_c(m); + const cartesi::machine_config cpp_config = cpp_m->get_initial_config(); *config = cm_set_temp_string(cartesi::to_json(cpp_config).dump()); return cm_result_success(); } catch (...) { @@ -570,12 +632,45 @@ cm_error cm_get_initial_config(const cm_machine *m, const char **config) try { return cm_result_failure(); } -cm_error cm_get_default_config(const char **config) try { +cm_error cm_get_runtime_config(const cm_machine *m, const char **runtime_config) try { + if (runtime_config == nullptr) { + throw std::invalid_argument("invalid runtime_config output"); + } + const auto *cpp_m = convert_from_c(m); + const cartesi::machine_runtime_config cpp_runtime_config = cpp_m->get_runtime_config(); + *runtime_config = cm_set_temp_string(cartesi::to_json(cpp_runtime_config).dump()); + return cm_result_success(); +} catch (...) { + if (runtime_config) { + *runtime_config = nullptr; + } + return cm_result_failure(); +} + +cm_error cm_set_runtime_config(cm_machine *m, const char *runtime_config) try { + if (runtime_config == nullptr) { + throw std::invalid_argument("invalid machine runtime configuration"); + } + cartesi::machine_runtime_config r = cartesi::from_json(runtime_config); + auto *cpp_m = convert_from_c(m); + cpp_m->set_runtime_config(r); + return cm_result_success(); +} catch (...) { + return cm_result_failure(); +} + +cm_error cm_get_default_config(const cm_machine *m, const char **config) try { if (config == nullptr) { throw std::invalid_argument("invalid config output"); } - const cartesi::machine_config cpp_config = cartesi::machine::get_default_config(); - *config = cm_set_temp_string(cartesi::to_json(cpp_config).dump()); + if (m != nullptr) { + const auto *cpp_m = convert_from_c(m); + const cartesi::machine_config cpp_config = cpp_m->get_default_config(); + *config = cm_set_temp_string(cartesi::to_json(cpp_config).dump()); + } else { + const cartesi::machine_config cpp_config = cartesi::machine::get_default_config(); + *config = cm_set_temp_string(cartesi::to_json(cpp_config).dump()); + } return cm_result_success(); } catch (...) { if (config) { @@ -586,44 +681,28 @@ cm_error cm_get_default_config(const char **config) try { cm_error cm_replace_memory_range(cm_machine *m, uint64_t start, uint64_t length, bool shared, const char *image_filename) try { - auto *cpp_machine = convert_from_c(m); + auto *cpp_m = convert_from_c(m); cartesi::memory_range_config cpp_range; cpp_range.start = start; cpp_range.length = length; cpp_range.shared = shared; cpp_range.image_filename = image_filename ? image_filename : ""; - cpp_machine->replace_memory_range(cpp_range); + cpp_m->replace_memory_range(cpp_range); return cm_result_success(); } catch (...) { return cm_result_failure(); } -void cm_destroy(cm_machine *m) { +void cm_delete(cm_machine *m) { if (m != nullptr) { - auto *cpp_machine = convert_from_c(m); - delete cpp_machine; + auto *cpp_m = convert_from_c(m); + delete cpp_m; } } -cm_error cm_snapshot(cm_machine *m) try { - auto *cpp_machine = convert_from_c(m); - cpp_machine->snapshot(); - return cm_result_success(); -} catch (...) { - return cm_result_failure(); -} - -cm_error cm_commit(cm_machine *m) try { - auto *cpp_machine = convert_from_c(m); - cpp_machine->commit(); - return cm_result_success(); -} catch (...) { - return cm_result_failure(); -} - -cm_error cm_rollback(cm_machine *m) try { - auto *cpp_machine = convert_from_c(m); - cpp_machine->rollback(); +cm_error cm_destroy(cm_machine *m) try { + auto *cpp_m = convert_from_c(m); + cpp_m->destroy(); return cm_result_success(); } catch (...) { return cm_result_failure(); @@ -633,8 +712,8 @@ cm_error cm_get_memory_ranges(const cm_machine *m, const char **ranges) try { if (ranges == nullptr) { throw std::invalid_argument("invalid memory range output"); } - const auto *cpp_machine = convert_from_c(m); - const cartesi::machine_memory_range_descrs cpp_ranges = cpp_machine->get_memory_ranges(); + const auto *cpp_m = convert_from_c(m); + const cartesi::machine_memory_range_descrs cpp_ranges = cpp_m->get_memory_ranges(); *ranges = cm_set_temp_string(cartesi::to_json(cpp_ranges).dump()); return cm_result_success(); } catch (...) { @@ -649,14 +728,13 @@ cm_error cm_receive_cmio_request(const cm_machine *m, uint8_t *cmd, uint16_t *re if (length == nullptr) { throw std::invalid_argument("invalid length output"); } - const auto *cpp_machine = convert_from_c(m); + const auto *cpp_m = convert_from_c(m); // NOTE(edubart): This can be implemented on top of other APIs, // implementing in the C++ machine class would add lot of boilerplate code in all interfaces. - if (!cpp_machine->read_reg(cartesi::machine::reg::iflags_x) && - !cpp_machine->read_reg(cartesi::machine::reg::iflags_y)) { + if (!cpp_m->read_reg(cartesi::machine::reg::iflags_x) && !cpp_m->read_reg(cartesi::machine::reg::iflags_y)) { throw std::runtime_error{"machine is not yielded"}; } - const uint64_t tohost = cpp_machine->read_reg(cartesi::machine::reg::htif_tohost); + const uint64_t tohost = cpp_m->read_reg(cartesi::machine::reg::htif_tohost); const uint8_t tohost_cmd = cartesi::HTIF_CMD_FIELD(tohost); const uint16_t tohost_reason = cartesi::HTIF_REASON_FIELD(tohost); const uint32_t tohost_data = cartesi::HTIF_DATA_FIELD(tohost); @@ -677,7 +755,7 @@ cm_error cm_receive_cmio_request(const cm_machine *m, uint8_t *cmd, uint16_t *re if (data_length > *length) { throw std::invalid_argument{"data buffer length is too small"}; } - cpp_machine->read_memory(cartesi::PMA_CMIO_TX_BUFFER_START, data, data_length); + cpp_m->read_memory(cartesi::PMA_CMIO_TX_BUFFER_START, data, data_length); } } if (cmd) { @@ -704,8 +782,8 @@ cm_error cm_receive_cmio_request(const cm_machine *m, uint8_t *cmd, uint16_t *re } cm_error cm_send_cmio_response(cm_machine *m, uint16_t reason, const uint8_t *data, uint64_t length) try { - auto *cpp_machine = convert_from_c(m); - cpp_machine->send_cmio_response(reason, data, length); + auto *cpp_m = convert_from_c(m); + cpp_m->send_cmio_response(reason, data, length); return cm_result_success(); } catch (...) { return cm_result_failure(); @@ -716,9 +794,9 @@ cm_error cm_log_send_cmio_response(cm_machine *m, uint16_t reason, const uint8_t if (log == nullptr) { throw std::invalid_argument("invalid access log output"); } - auto *cpp_machine = convert_from_c(m); + auto *cpp_m = convert_from_c(m); cartesi::access_log::type cpp_log_type(log_type); - cartesi::access_log cpp_log = cpp_machine->log_send_cmio_response(reason, data, length, cpp_log_type); + cartesi::access_log cpp_log = cpp_m->log_send_cmio_response(reason, data, length, cpp_log_type); *log = cm_set_temp_string(cartesi::to_json(cpp_log).dump()); return cm_result_success(); } catch (...) { @@ -728,7 +806,7 @@ cm_error cm_log_send_cmio_response(cm_machine *m, uint16_t reason, const uint8_t return cm_result_failure(); } -cm_error cm_verify_send_cmio_response(uint16_t reason, const uint8_t *data, uint64_t length, +cm_error cm_verify_send_cmio_response(const cm_machine *m, uint16_t reason, const uint8_t *data, uint64_t length, const cm_hash *root_hash_before, const char *log, const cm_hash *root_hash_after) try { if (log == nullptr) { throw std::invalid_argument("invalid access log"); @@ -737,8 +815,13 @@ cm_error cm_verify_send_cmio_response(uint16_t reason, const uint8_t *data, uint cartesi::from_json>(log).value(); const cartesi::machine::hash_type cpp_root_hash_before = convert_from_c(root_hash_before); const cartesi::machine::hash_type cpp_root_hash_after = convert_from_c(root_hash_after); - cartesi::machine::verify_send_cmio_response(reason, data, length, cpp_root_hash_before, cpp_log, - cpp_root_hash_after); + if (m != nullptr) { + const auto *cpp_m = convert_from_c(m); + cpp_m->verify_send_cmio_response(reason, data, length, cpp_root_hash_before, cpp_log, cpp_root_hash_after); + } else { + cartesi::machine::verify_send_cmio_response(reason, data, length, cpp_root_hash_before, cpp_log, + cpp_root_hash_after); + } return cm_result_success(); } catch (...) { return cm_result_failure(); diff --git a/src/machine-c-api.h b/src/machine-c-api.h index 6f227edfa..1cc2139e5 100644 --- a/src/machine-c-api.h +++ b/src/machine-c-api.h @@ -294,7 +294,7 @@ typedef enum cm_reg { /// \brief Storage for machine hash. typedef uint8_t cm_hash[CM_HASH_SIZE]; -/// \brief Machine instance handle. +/// \brief Machine object handle. /// \details It's used only as an opaque handle to pass machine objects through the C API. typedef struct cm_machine cm_machine; @@ -304,64 +304,125 @@ typedef struct cm_machine cm_machine; /// \brief Returns the error message set by the very last C API call. /// \returns A C string, guaranteed to remain valid only until the next CM_API function call. -/// \details The string returned by this function must not be changed nor deallocated, -/// and remains valid until next CM_API function that can return a cm_error code is called. +/// \details The string returned by this function must not be changed nor deallocated, and remains valid until +/// next CM_API function that can return a cm_error code is called. /// Must be called from the same thread that called the function that produced the error. -/// In case the last call was successful, it returns an empty string. -/// (Do not use the empty string as an indication that the previous call was successful.) -/// (Instead, use the return code of the previous call itself.) +/// In case the last CM_API function call on that thread was successful, returns an empty string. +/// \warning Do not use the empty string as an indication that the previous call was successful. +/// This would be error-prone since calls from different threads may see different strings. +/// Instead, use the return code of the previous call itself. CM_API const char *cm_get_last_error_message(); /// \brief Obtains a JSON object with the default machine config as a string. -/// \param config Receives the default configuration as a JSON object in a string, -/// guaranteed to remain valid only until the next CM_API function is called from the same thread. +/// \param m Pointer to a machine object. Can be NULL (for local machines). +/// \param config Receives the default configuration as a JSON object in a string, guaranteed to remain valid only until +/// the next CM_API function is called from the same thread. Set to NULL on failure. /// \returns 0 for success, non zero code for error. /// \details The returned config is not sufficient to run a machine. -/// Additional configurations, such as RAM length, RAM image, flash drives, -/// and entrypoint are still needed. -CM_API cm_error cm_get_default_config(const char **config); +/// Additional configurations, such as RAM length, RAM image, flash drives, and entrypoint are still needed. +CM_API cm_error cm_get_default_config(const cm_machine *m, const char **config); /// \brief Gets the address of any x, f, or control state register. +/// \param m Pointer to a machine object. Can be NULL (for local machines). /// \param reg The register. /// \param val Receives address of the register. /// \returns 0 for success, non zero code for error. -/// \details The current implementation of this function is slow when the word falls -/// in a memory range mapped to a device. -CM_API cm_error cm_get_reg_address(cm_reg reg, uint64_t *val); +CM_API cm_error cm_get_reg_address(const cm_machine *m, cm_reg reg, uint64_t *val); // ----------------------------------------------------------------------------- // Machine API functions // ----------------------------------------------------------------------------- +/// \brief Creates a new machine object. +/// \param m Pointer to the existing machine object (can be NULL). +/// \param new_m Receives the pointer to the new machine object. Set to NULL on failure. +/// \returns 0 for success, non zero code for error. +/// \details If the parameter \p m is not NULL, the new machine object will be of +/// the same type. Otherwise, it will be a local machine. +/// Regardless, a newly created object is empty (does not hold a machine instance). +/// Use cm_create() or cm_load() to instantiate a machine into the object. +/// Use cm_delete() to delete the object. +CM_API cm_error cm_new(const cm_machine *m, cm_machine **new_m); + +/// \brief Checks if object is empty (does not holds a machine instance). +/// \param m Pointer to the existing machine object. +/// \param yes Receives true if empty, false otherwise. +/// \returns 0 for success, non zero code for error. +CM_API cm_error cm_is_empty(const cm_machine *m, bool *yes); + +/// \brief Deletes a machine object. +/// \param m Pointer to the existing machine object (can be NULL). +/// \details The pointer to the machine object must not be used after this call. +/// \details If the machine object is not empty (has a machine instance), +/// silently calls cm_destroy() to destroy the instance. +CM_API void cm_delete(cm_machine *m); + /// \brief Creates a new machine instance from configuration. +/// \param m Pointer to an empty machine object (does not hold a machine instance). +/// \param config Machine configuration as a JSON object in a string. +/// \param runtime_config Machine runtime configuration as a JSON object in a string (can be NULL). +/// \returns 0 for success, non zero code for error. +/// \details Use cm_destroy() to destroy the machine istance and remove it from the object. +CM_API cm_error cm_create(cm_machine *m, const char *config, const char *runtime_config); + +/// \brief Combines cm_new() and cm_create() for convenience. /// \param config Machine configuration as a JSON object in a string. -/// \param runtime_config Machine runtime configuration as a JSON object in a -/// string (can be NULL). -/// \param new_machine Receives the pointer to new machine instance. +/// \param runtime_config Machine runtime configuration as a JSON object in a string (can be NULL). +/// \param new_m Receives the pointer to the new machine object with a machine instance. Set to NULL on failure. /// \returns 0 for success, non zero code for error. -CM_API cm_error cm_create(const char *config, const char *runtime_config, cm_machine **new_machine); +/// \details Use cm_destroy() to destroy the machine istance and remove it from the object. +/// \details Use cm_delete() to delete the object. +CM_API cm_error cm_create_new(const char *config, const char *runtime_config, cm_machine **new_m); /// \brief Loads a new machine instance from a previously stored directory. +/// \param m Pointer to an empty machine object (does not hold a machine instance). /// \param dir Directory where previous machine is stored. /// \param runtime_config Machine runtime configuration as a JSON object in a string (can be NULL). -/// \param new_machine Receives the pointer to new machine instance. /// \returns 0 for success, non zero code for error. -CM_API cm_error cm_load(const char *dir, const char *runtime_config, cm_machine **new_machine); +/// \details Use cm_destroy() to destroy the machine istance and remove it from the object. +CM_API cm_error cm_load(cm_machine *m, const char *dir, const char *runtime_config); -/// \brief Destroy an attached machine and releases handle. -/// \param m Pointer to the existing machine handle (can be NULL). -/// \details The machine handle must not be used after this call. -CM_API void cm_destroy(cm_machine *m); +/// \brief Combines cm_new() and cm_load() for convenience. +/// \param dir Directory where previous machine is stored. +/// \param runtime_config Machine runtime configuration as a JSON object in a string (can be NULL). +/// \param new_m Receives the pointer to the new machine object with a machine instance. Set to NULL on failure. +/// \returns 0 for success, non zero code for error. +/// \details Use cm_destroy() to destroy the machine istance and remove it from the object. +/// \details Use cm_delete() to delete the object. +CM_API cm_error cm_load_new(const char *dir, const char *runtime_config, cm_machine **new_m); /// \brief Stores a machine instance to a directory, serializing its entire state. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param dir Directory where the machine will be stored. /// \returns 0 for success, non zero code for error. -/// \details The function refuses to store into an existing directory. +/// \details The function refuses to store into an existing directory (it will not overwrite an existing machine). CM_API cm_error cm_store(const cm_machine *m, const char *dir); +/// \brief Destroy a machine instance and remove it from the object. +/// \param m Pointer to a non-empty machine object (holds a machine instance). +/// \returns 0 for success, non zero code for error. +/// \details cm_delete() may fail silently when implicitly calling cm_destroy(). +/// To make sure the machine was successfully destroyed, call cm_destroy() explicitly. +/// \details This function does not delete the machine object. +/// You must still call cm_delete() afterwards. +CM_API cm_error cm_destroy(cm_machine *m); + +/// \brief Changes the machine runtime configuration. +/// \param m Pointer to a non-empty machine object (holds a machine instance). +/// \param runtime_config Machine runtime configuration as a JSON object in a string (can be NULL). +/// \returns 0 for success, non zero code for error. +CM_API cm_error cm_set_runtime_config(cm_machine *m, const char *runtime_config); + +/// \brief Changes the machine runtime config. +/// \param m Pointer to a non-empty machine object (holds a machine instance). +/// \param runtime_config Receives the runtime configuration as a JSON object in a string, +/// guaranteed to remain valid only until the next CM_API function is called from the same thread. +/// Set to NULL on failure. +/// \returns 0 for success, non zero code for error. +CM_API cm_error cm_get_runtime_config(const cm_machine *m, const char **runtime_config); + /// \brief Replaces a memory range. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param start Range start physical address. /// \param length Range length in bytes. /// \param shared[ni] If true, changes to the range from inside the machine will be @@ -375,27 +436,28 @@ CM_API cm_error cm_replace_memory_range(cm_machine *m, uint64_t start, uint64_t const char *image_filename); /// \brief Returns a JSON object with the machine config used to initialize the machine. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param config Receives the initial configuration as a JSON object in a string, /// guaranteed to remain valid only until the next CM_API function is called from the same thread. +/// Set to NULL on failure. /// \returns 0 for success, non zero code for error. CM_API cm_error cm_get_initial_config(const cm_machine *m, const char **config); /// \brief Returns a list with all memory ranges in the machine. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param ranges Receives the memory ranges as a JSON object in a string, /// guaranteed to remain valid only until the next CM_API function is called from the same thread. /// \returns 0 for success, non zero code for error. CM_API cm_error cm_get_memory_ranges(const cm_machine *m, const char **ranges); /// \brief Obtains the root hash of the Merkle tree. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param hash Valid pointer to cm_hash structure that receives the hash. /// \returns 0 for success, non zero code for error. CM_API cm_error cm_get_root_hash(const cm_machine *m, cm_hash *hash); /// \brief Obtains the proof for a node in the machine state Merkle tree. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param address Address of target node. Must be aligned to a 2^log2_size boundary. /// \param log2_size The log base 2 of the size subtended by target node. /// Must be between CM_TREE_LOG2_WORD_SIZE (for a word) and CM_TREE_LOG2_ROOT_SIZE @@ -410,30 +472,29 @@ CM_API cm_error cm_get_proof(const cm_machine *m, uint64_t address, int32_t log2 // ------------------------------------ /// \brief Reads the value of a word in the machine state, by its physical address. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param address Word address (aligned to 64-bit boundary). /// \param val Receives word value. /// \returns 0 for success, non zero code for error. -/// \details The current implementation of this function is slow when the word falls -/// in a memory range mapped to a device. +/// \warning The implementation is slow when the word falls in a memory range mapped to a device. CM_API cm_error cm_read_word(const cm_machine *m, uint64_t address, uint64_t *val); /// \brief Reads the value of a register. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param reg Register to read. /// \param val Receives the value. /// \returns 0 for success, non zero code for error. CM_API cm_error cm_read_reg(const cm_machine *m, cm_reg reg, uint64_t *val); /// \brief Writes the value of a register. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param reg Register to write. /// \param val Value to write. /// \returns 0 for success, non zero code for error. CM_API cm_error cm_write_reg(cm_machine *m, cm_reg reg, uint64_t val); /// \brief Reads a chunk of data from a machine memory range, by its physical address. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param address Physical address to start reading. /// \param data Receives chunk of memory. /// \param length Size of chunk in bytes. @@ -442,7 +503,7 @@ CM_API cm_error cm_write_reg(cm_machine *m, cm_reg reg, uint64_t val); CM_API cm_error cm_read_memory(const cm_machine *m, uint64_t address, uint8_t *data, uint64_t length); /// \brief Writes a chunk of data to a machine memory range, by its physical address. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param address Physical address to start writing. /// \param data Source for chunk of data. /// \param length Size of chunk in bytes. @@ -452,7 +513,7 @@ CM_API cm_error cm_read_memory(const cm_machine *m, uint64_t address, uint8_t *d CM_API cm_error cm_write_memory(cm_machine *m, uint64_t address, const uint8_t *data, uint64_t length); /// \brief Reads a chunk of data from a machine memory range, by its virtual memory. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param address Virtual address to start reading. /// \param data Receives chunk of memory. /// \param length Size of chunk in bytes. @@ -461,7 +522,7 @@ CM_API cm_error cm_write_memory(cm_machine *m, uint64_t address, const uint8_t * CM_API cm_error cm_read_virtual_memory(cm_machine *m, uint64_t address, uint8_t *data, uint64_t length); /// \brief Writes a chunk of data to a machine memory range, by its virtual address. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param address Virtual address to start writing. /// \param data Source for chunk of data. /// \param length Size of chunk in bytes. @@ -470,7 +531,7 @@ CM_API cm_error cm_read_virtual_memory(cm_machine *m, uint64_t address, uint8_t CM_API cm_error cm_write_virtual_memory(cm_machine *m, uint64_t address, const uint8_t *data, uint64_t length); /// \brief Translates a virtual memory address to its corresponding physical memory address. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param vaddr Virtual address to translate. /// \param paddr Receives the physical memory address. /// \returns 0 for success, non zero code for error. @@ -478,84 +539,62 @@ CM_API cm_error cm_write_virtual_memory(cm_machine *m, uint64_t address, const u CM_API cm_error cm_translate_virtual_address(cm_machine *m, uint64_t vaddr, uint64_t *paddr); /// \brief Reads the value of the CM_REG_MCYCLE. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param val Receives the value. /// \returns 0 for success, non zero code for error. CM_API cm_error cm_read_mcycle(const cm_machine *m, uint64_t *val); /// \brief Reads the value of the X flag in CM_REG_IFLAGS. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param val Receives the value. /// \returns 0 for success, non zero code for error. CM_API cm_error cm_read_iflags_X(const cm_machine *m, bool *val); /// \brief Reads the value of the Y flag in CM_REG_IFLAGS. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param val Receives the value. /// \returns 0 for success, non zero code for error. CM_API cm_error cm_read_iflags_Y(const cm_machine *m, bool *val); /// \brief Resets the value of the Y flag in CM_REG_IFLAGS. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \returns 0 for success, non zero code for error. CM_API cm_error cm_reset_iflags_Y(cm_machine *m); /// \brief Sets the Y flag in CM_REG_IFLAGS. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \returns 0 for success, non zero code for error. CM_API cm_error cm_set_iflags_Y(cm_machine *m); /// \brief Reads the value of the H flag in CM_REG_IFLAGS. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param val Receives the value. /// \returns 0 for success, non zero code for error. CM_API cm_error cm_read_iflags_H(const cm_machine *m, bool *val); /// \brief Reads the value of CM_REG_UARCH_CYCLE. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param val Receives the value. /// \returns 0 for success, non zero code for error. CM_API cm_error cm_read_uarch_cycle(const cm_machine *m, uint64_t *val); /// \brief Reads the value of CM_REG_UARCH_HALT_FLAG. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param val Receives the value. /// \returns 0 for success, non zero code for error. CM_API cm_error cm_read_uarch_halt_flag(const cm_machine *m, bool *val); /// \brief Sets the value of CM_REG_UARCH_HALT_FLAG. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \returns 0 for success, non zero code for error. CM_API cm_error cm_set_uarch_halt_flag(cm_machine *m); -// ------------------------------------ -// Rolling back -// ------------------------------------ - -/// \brief Replaces the current snapshot with a copy of the current machine state. -/// \param m Pointer to a valid machine handle. -/// \returns 0 for success, non zero code for error. -/// \detail This function is ignored unless the machine is remote. -CM_API cm_error cm_snapshot(cm_machine *m); - -/// \brief Delete current snapshot. -/// \param m Pointer to a valid machine handle. -/// \returns 0 for success, non zero code for error. -/// \detail This function is ignored unless the machine is remote. -CM_API cm_error cm_commit(cm_machine *m); - -/// \brief Replaces machine state with copy in current snapshot, and then delete snapshot. -/// \param m Pointer to a valid machine handle. -/// \returns 0 for success, non zero code for error. -/// \detail This function is ignored unless the machine is remote. -CM_API cm_error cm_rollback(cm_machine *m); - // ------------------------------------ // Running // ------------------------------------ /// \brief Runs the machine until CM_REG_MCYCLE reaches mcycle_end, machine yields, or halts. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param mcycle_end End cycle value. /// \param break_reason Receives reason for returning (can be NULL). /// \returns 0 for success, non zero code for error. @@ -563,19 +602,19 @@ CM_API cm_error cm_rollback(cm_machine *m); CM_API cm_error cm_run(cm_machine *m, uint64_t mcycle_end, cm_break_reason *break_reason); /// \brief Runs the machine microarchitecture until CM_REG_UARCH_CYCLE reaches uarch_cycle_end or it halts. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param uarch_cycle_end End micro cycle value. /// \param uarch_break_reason Receives reason for returning (can be NULL). /// \returns 0 for success, non zero code for error. CM_API cm_error cm_run_uarch(cm_machine *m, uint64_t uarch_cycle_end, cm_uarch_break_reason *uarch_break_reason); /// \brief Resets the entire microarchitecture state to pristine values. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \returns 0 for success, non zero code for error. CM_API cm_error cm_reset_uarch(cm_machine *m); /// \brief Receives a cmio request. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param cmd Receives the yield command (manual or automatic). /// \param reason Receives the yield reason (see below). /// \param data Receives the yield data. If NULL, length will still be set without reading any data. @@ -591,7 +630,7 @@ CM_API cm_error cm_receive_cmio_request(const cm_machine *m, uint8_t *cmd, uint1 uint64_t *length); /// \brief Sends a cmio response. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param reason Reason for sending the response. /// \param data Response data to send. /// \param length Length of response data. @@ -605,7 +644,7 @@ CM_API cm_error cm_send_cmio_response(cm_machine *m, uint16_t reason, const uint // ------------------------------------ /// \brief Runs the machine in the microarchitecture for one micro cycle logging all accesses to the state. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param log_type Type of access log to generate. /// \param log Receives the state access log as a JSON object in a string, /// guaranteed to remain valid only until the next CM_API function is called from the same thread. @@ -613,7 +652,7 @@ CM_API cm_error cm_send_cmio_response(cm_machine *m, uint16_t reason, const uint CM_API cm_error cm_log_step_uarch(cm_machine *m, int32_t log_type, const char **log); /// \brief Resets the entire microarchitecture state to pristine values logging all accesses to the state. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param log_type Type of access log to generate. /// \param log Receives the state access log as a JSON object in a string, /// guaranteed to remain valid only until the next CM_API function is called from the same thread. @@ -621,7 +660,7 @@ CM_API cm_error cm_log_step_uarch(cm_machine *m, int32_t log_type, const char ** CM_API cm_error cm_log_reset_uarch(cm_machine *m, int32_t log_type, const char **log); /// \brief Sends a cmio response logging all accesses to the state. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param reason Reason for sending the response. /// \param data Response data to send. /// \param length Length of response data. @@ -637,20 +676,25 @@ CM_API cm_error cm_log_send_cmio_response(cm_machine *m, uint16_t reason, const // ------------------------------------ /// \brief Checks the validity of a state transition produced by cm_log_step_uarch. +/// \param m Pointer to a machine object. Can be NULL (for local machines). /// \param root_hash_before State hash before step. /// \param log State access log to be verified as a JSON object in a string. /// \param root_hash_after State hash after step. /// \returns 0 for success, non zero code for error. -CM_API cm_error cm_verify_step_uarch(const cm_hash *root_hash_before, const char *log, const cm_hash *root_hash_after); +CM_API cm_error cm_verify_step_uarch(const cm_machine *m, const cm_hash *root_hash_before, const char *log, + const cm_hash *root_hash_after); /// \brief Checks the validity of a state transition produced by cm_log_verify_reset_uarch. +/// \param m Pointer to a machine object. Can be NULL (for local machines). /// \param root_hash_before State hash before reset. /// \param log State access log to be verified as a JSON object in a string. /// \param root_hash_after State hash after reset. /// \returns 0 for success, non zero code for error. -CM_API cm_error cm_verify_reset_uarch(const cm_hash *root_hash_before, const char *log, const cm_hash *root_hash_after); +CM_API cm_error cm_verify_reset_uarch(const cm_machine *m, const cm_hash *root_hash_before, const char *log, + const cm_hash *root_hash_after); /// \brief Checks the validity of a state transition produced by cm_log_send_cmio_response. +/// \param m Pointer to a machine object. Can be NULL (for local machines). /// \param reason Reason for sending the response. /// \param data The response sent when the log was generated. /// \param length Length of response. @@ -658,7 +702,7 @@ CM_API cm_error cm_verify_reset_uarch(const cm_hash *root_hash_before, const cha /// \param log State access log to be verified as a JSON object in a string. /// \param root_hash_after State hash after response. /// \returns 0 for success, non zero code for error. -CM_API cm_error cm_verify_send_cmio_response(uint16_t reason, const uint8_t *data, uint64_t length, +CM_API cm_error cm_verify_send_cmio_response(const cm_machine *m, uint16_t reason, const uint8_t *data, uint64_t length, const cm_hash *root_hash_before, const char *log, const cm_hash *root_hash_after); // ------------------------------------ @@ -666,14 +710,14 @@ CM_API cm_error cm_verify_send_cmio_response(uint16_t reason, const uint8_t *dat // ------------------------------------ /// \brief Verifies integrity of Merkle tree against current machine state. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param result True if tree is self-consistent, false otherwise. /// \returns 0 for success, non zero code for error. /// \details This method is used only for emulator internal tests. CM_API cm_error cm_verify_merkle_tree(cm_machine *m, bool *result); /// \brief Verify integrity of dirty page maps. -/// \param m Pointer to a valid machine handle. +/// \param m Pointer to a non-empty machine object (holds a machine instance). /// \param result True if dirty page maps are consistent, false otherwise. /// \returns 0 for success, non zero code for error. /// \details This method is used only for emulator internal tests. diff --git a/src/machine.cpp b/src/machine.cpp index 9b8f0246a..fb12de8f3 100644 --- a/src/machine.cpp +++ b/src/machine.cpp @@ -585,6 +585,25 @@ bool machine::has_htif_console() const { return static_cast(read_reg(reg::htif_iconsole) & (1 << HTIF_CONSOLE_CMD_GETCHAR)); } +/// \brief Returns copy of initialization config. +const machine_config &machine::get_initial_config(void) const { + return m_c; +} + +/// \brief Returns the machine runtime config. +const machine_runtime_config &machine::get_runtime_config(void) const { + return m_r; +} + +/// \brief Changes the machine runtime config. +void machine::set_runtime_config(const machine_runtime_config &r) { + if (r.htif.no_console_putchar != m_r.htif.no_console_putchar) { + throw std::runtime_error{"cannot change htif runtime configuration"}; + } + m_r = r; + m_s.soft_yield = m_r.soft_yield; +} + machine_config machine::get_serialization_config(void) const { if (read_reg(reg::iunrep)) { throw std::runtime_error{"cannot serialize configuration of unreproducible machines"}; diff --git a/src/machine.h b/src/machine.h index 9d5472c3b..ea00b2635 100644 --- a/src/machine.h +++ b/src/machine.h @@ -349,6 +349,7 @@ class machine final { static void verify_reset_uarch(const hash_type &root_hash_before, const access_log &log, const hash_type &root_hash_after); + /// \brief Returns copy of default machine config static machine_config get_default_config(void); /// \brief Returns machine state for direct access. @@ -533,14 +534,15 @@ class machine final { machine_config get_serialization_config(void) const; /// \brief Returns copy of initialization config. - const machine_config &get_initial_config(void) const { - return m_c; - } + const machine_config &get_initial_config(void) const; /// \brief Returns the machine runtime config. - const machine_runtime_config &get_runtime_config(void) const { - return m_r; - } + const machine_runtime_config &get_runtime_config(void) const; + + /// \brief Changes the machine runtime config. + /// \param range Configuration of the new memory range. + /// \details Some runtime options cannot be changed. + void set_runtime_config(const machine_runtime_config &r); /// \brief Replaces a memory range. /// \param range Configuration of the new memory range. diff --git a/src/os.cpp b/src/os.cpp index 9b28603cf..99cf8c35f 100644 --- a/src/os.cpp +++ b/src/os.cpp @@ -801,7 +801,7 @@ static void sig_alrm(int) { // the parent returns the final child pid // the final child returns 0 // on error, the parent throws and the final child does not return -int os_double_fork_or_throw(int newpgid) { +int os_double_fork_or_throw(bool emancipate) { int fd[2] = {-1, -1}; struct sigaction chld_act {}; bool restore_sigchld = false; @@ -857,7 +857,7 @@ int os_double_fork_or_throw(int newpgid) { close(fd[0]); fd[0] = -1; // break out into our own program group, if requested - if (newpgid) { + if (emancipate) { setpgid(0, 0); } // write fpid so parent can read @@ -938,11 +938,11 @@ int os_double_fork_or_throw(int newpgid) { } } -int os_double_fork(int newpgid, const char **err_msg) { +int os_double_fork(bool emancipate, const char **err_msg) { static THREAD_LOCAL std::string error_storage; try { *err_msg = nullptr; - return os_double_fork_or_throw(newpgid); + return os_double_fork_or_throw(emancipate); } catch (std::exception &e) { error_storage = e.what(); *err_msg = error_storage.c_str(); diff --git a/src/os.h b/src/os.h index 8cd795e3c..132cbbcc3 100644 --- a/src/os.h +++ b/src/os.h @@ -148,19 +148,19 @@ void os_disable_sigpipe(); void os_sleep_us(uint64_t timeout_us); // \brief Double-fork implementation -// \param newpgid If non-zero, the grand-child breaks out of parent program group into its own +// \param emancipate If true, the grand-child breaks out of grand-parent program group into its own. // \returns In case of success, grand-child returns 0 and the parent returns the grand-child pid. // In case of error, parent throws and there is no grand-child. -int os_double_fork_or_throw(int newpgid); +int os_double_fork_or_throw(bool emancipate); // \brief Double-fork implementation -// \param newpgid If non-zero, the grand-child breaks out of parent program group into its own +// \param emancipate If true, the grand-child breaks out of grand-parent program group into its own. // \err_msg In case of error, returns a string with an error message, guaranteed // to remain valid only until the the next time this same function is called // again on the same thread. Set to nullptr otherwise. // \returns In case of success, grand-child returns 0 and the parent returns the grand-child pid. // In case of error, parent returns -1 and there is no grand-child. -int os_double_fork(int newpgid, const char **err_msg); +int os_double_fork(bool emancipate, const char **err_msg); } // namespace cartesi diff --git a/src/virtual-machine.cpp b/src/virtual-machine.cpp index 74c1a89c2..983d57b22 100644 --- a/src/virtual-machine.cpp +++ b/src/virtual-machine.cpp @@ -18,11 +18,21 @@ namespace cartesi { -virtual_machine::virtual_machine(const machine_config &c, const machine_runtime_config &r) : - m_machine(new machine(c, r)) {} +i_virtual_machine *virtual_machine::do_clone_empty(void) const { + return new virtual_machine(); +} + +bool virtual_machine::do_is_empty(void) const { + return m_machine == nullptr; +} -virtual_machine::virtual_machine(const std::string &dir, const machine_runtime_config &r) : - m_machine(new machine(dir, r)) {} +void virtual_machine::do_create(const machine_config &config, const machine_runtime_config &runtime) { + m_machine = new machine(config, runtime); +} + +void virtual_machine::do_load(const std::string &directory, const machine_runtime_config &runtime) { + m_machine = new machine(directory, runtime); +} virtual_machine::~virtual_machine(void) { delete m_machine; @@ -43,8 +53,8 @@ const machine *virtual_machine::get_machine(void) const { return m_machine; } -void virtual_machine::do_store(const std::string &dir) const { - get_machine()->store(dir); +void virtual_machine::do_store(const std::string &directory) const { + get_machine()->store(directory); } interpreter_break_reason virtual_machine::do_run(uint64_t mcycle_end) { @@ -111,21 +121,17 @@ machine_config virtual_machine::do_get_initial_config(void) const { return get_machine()->get_initial_config(); } -void virtual_machine::do_destroy() { - delete m_machine; - m_machine = nullptr; +machine_runtime_config virtual_machine::do_get_runtime_config(void) const { + return get_machine()->get_runtime_config(); } -void virtual_machine::do_snapshot(void) { - throw std::runtime_error("snapshot is not supported"); +void virtual_machine::do_set_runtime_config(const machine_runtime_config &r) { + return get_machine()->set_runtime_config(r); } -void virtual_machine::do_commit(void) { - // no-op, we are always committed -} - -void virtual_machine::do_rollback(void) { - throw std::runtime_error("rollback is not supported"); +void virtual_machine::do_destroy() { + delete get_machine(); + m_machine = nullptr; } void virtual_machine::do_reset_uarch() { @@ -153,4 +159,27 @@ access_log virtual_machine::do_log_send_cmio_response(uint16_t reason, const uns return get_machine()->log_send_cmio_response(reason, data, length, log_type); } +uint64_t virtual_machine::do_get_reg_address(reg r) const { + return machine::get_reg_address(r); +} + +machine_config virtual_machine::do_get_default_config(void) const { + return machine::get_default_config(); +} + +void virtual_machine::do_verify_step_uarch(const hash_type &root_hash_before, const access_log &log, + const hash_type &root_hash_after) const { + return machine::verify_step_uarch(root_hash_before, log, root_hash_after); +} + +void virtual_machine::do_verify_reset_uarch(const hash_type &root_hash_before, const access_log &log, + const hash_type &root_hash_after) const { + return machine::verify_reset_uarch(root_hash_before, log, root_hash_after); +} + +void virtual_machine::do_verify_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length, + const hash_type &root_hash_before, const access_log &log, const hash_type &root_hash_after) const { + return machine::verify_send_cmio_response(reason, data, length, root_hash_before, log, root_hash_after); +} + } // namespace cartesi diff --git a/src/virtual-machine.h b/src/virtual-machine.h index c38e9be78..3de48a11f 100644 --- a/src/virtual-machine.h +++ b/src/virtual-machine.h @@ -26,12 +26,8 @@ namespace cartesi { /// \class virtual_machine /// \brief i_virtual_machine implementation pointing to a local machine instance class virtual_machine : public i_virtual_machine { - using machine = cartesi::machine; - machine *m_machine; - public: - virtual_machine(const machine_config &c, const machine_runtime_config &r = {}); - virtual_machine(const std::string &dir, const machine_runtime_config &r = {}); + virtual_machine(void) = default; virtual_machine(const virtual_machine &other) = delete; virtual_machine(virtual_machine &&other) noexcept = delete; virtual_machine &operator=(const virtual_machine &other) = delete; @@ -39,10 +35,12 @@ class virtual_machine : public i_virtual_machine { ~virtual_machine(void) override; private: - machine *get_machine(void); - const machine *get_machine(void) const; - void do_store(const std::string &dir) const override; + i_virtual_machine *do_clone_empty(void) const override; + bool do_is_empty(void) const override; + void do_create(const machine_config &config, const machine_runtime_config &runtime) override; + void do_load(const std::string &directory, const machine_runtime_config &runtime) override; interpreter_break_reason do_run(uint64_t mcycle_end) override; + void do_store(const std::string &directory) const override; access_log do_log_step_uarch(const access_log::type &log_type) override; machine_merkle_tree::proof_type do_get_proof(uint64_t address, int log2_size) const override; void do_get_root_hash(hash_type &hash) const override; @@ -58,10 +56,9 @@ class virtual_machine : public i_virtual_machine { uint64_t do_read_word(uint64_t address) const override; bool do_verify_dirty_page_maps(void) const override; machine_config do_get_initial_config(void) const override; - void do_snapshot() override; + machine_runtime_config do_get_runtime_config(void) const override; + void do_set_runtime_config(const machine_runtime_config &r) override; void do_destroy() override; - void do_commit() override; - void do_rollback() override; void do_reset_uarch() override; access_log do_log_reset_uarch(const access_log::type &log_type) override; uarch_interpreter_break_reason do_run_uarch(uint64_t uarch_cycle_end) override; @@ -69,6 +66,19 @@ class virtual_machine : public i_virtual_machine { void do_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length) override; access_log do_log_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length, const access_log::type &log_type) override; + uint64_t do_get_reg_address(reg r) const override; + machine_config do_get_default_config(void) const override; + void do_verify_step_uarch(const hash_type &root_hash_before, const access_log &log, + const hash_type &root_hash_after) const override; + void do_verify_reset_uarch(const hash_type &root_hash_before, const access_log &log, + const hash_type &root_hash_after) const override; + void do_verify_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length, + const hash_type &root_hash_before, const access_log &log, const hash_type &root_hash_after) const override; + + machine *get_machine(void); + const machine *get_machine(void) const; + + machine *m_machine = nullptr; }; } // namespace cartesi diff --git a/tests/lua/cartesi-machine-tests.lua b/tests/lua/cartesi-machine-tests.lua index f5e281b83..d8e6ff2a0 100755 --- a/tests/lua/cartesi-machine-tests.lua +++ b/tests/lua/cartesi-machine-tests.lua @@ -21,6 +21,7 @@ local util = require("cartesi.util") local test_util = require("cartesi.tests.util") local tabular = require("cartesi.tabular") local parallel = require("cartesi.parallel") +local jsonrpc -- Tests Cases -- format {"ram_image_file", number_of_cycles, halt_payload} @@ -330,8 +331,8 @@ where options are: uarch cycle. Only take effect with hash and step commands. (default: none) - --remote-address=
- use a remote cartesi machine listening to
instead of + --remote-address=: + use a remote cartesi machine listening to : instead of running a local cartesi machine. --output= @@ -376,12 +377,6 @@ and command can be: with a suffix multiplier (i.e., Ki, Mi, Gi for 2^10, 2^20, 2^30, respectively), or a left shift (e.g., 2 << 20). -
is one of the following formats: - : - unix: - - can be a host name, IPv4 or IPv6 address. - ]=], arg[0] )) @@ -572,8 +567,10 @@ end local command = assert(values[1], "missing command") assert(test_path, "missing test path") +local to_shutdown if remote_address then - protocol = require("cartesi." .. remote_protocol) + jsonrpc = require("cartesi.jsonrpc") + to_shutdown = jsonrpc.connect_server(remote_address):set_cleanup_call(jsonrpc.SHUTDOWN) end local function advance_machine(machine, max_mcycle) @@ -603,13 +600,6 @@ local function run_machine_with_uarch(machine, ctx, max_mcycle) run_machine(machine, ctx, max_mcycle, advance_machine_with_uarch) end -local function connect() - local remote = protocol.connect(remote_address) -- server will be shutdown when remote is collected - local version = - assert(remote.get_server_version(), "could not connect to remote cartesi machine at " .. remote_address) - return remote, version -end - local function build_machine(ram_image) local config = { ram = { @@ -630,10 +620,8 @@ local function build_machine(ram_image) }, } if remote_address then - if not remote then - remote = connect() - end - return assert(remote.machine(config, runtime)) + local jsonrpc_machine = assert(jsonrpc.connect_server(remote_address)) + return assert(jsonrpc_machine(config, runtime):set_cleanup_call(jsonrpc.SHUTDOWN)) end return assert(cartesi.machine(config, runtime)) end @@ -695,7 +683,7 @@ local function hash(tests) local ram_image = test[1] local expected_cycles = test[2] local expected_payload = test[3] or 0 - local machine = build_machine(ram_image) + local machine = build_machine(ram_image) local total_cycles = 0 local max_mcycle = 2 * expected_cycles while math.ult(machine:read_mcycle(), max_mcycle) do @@ -739,7 +727,6 @@ local function hash(tests) util.hexhash(machine:get_root_hash()), "\n" ) - machine:destroy() end end @@ -763,7 +750,7 @@ local function step(tests) local ram_image = test[1] local expected_cycles = test[2] local expected_payload = test[3] or 0 - local machine = build_machine(ram_image) + local machine = build_machine(ram_image) indentout(out, 1, "{\n") indentout(out, 2, '"test": "%s",\n', ram_image) if periodic_action then @@ -830,20 +817,18 @@ local function step(tests) then os.exit(1, true) end - machine:destroy() end out:write("]\n") end local function dump(tests) local ram_image = tests[1][1] - local machine = build_machine(ram_image) + local machine = build_machine(ram_image) for _, v in machine:get_memory_ranges() do local filename = string.format("%016x--%016x.bin", v.start, v.length) local file = assert(io.open(filename, "w")) assert(file:write(machine:read_memory(v.start, v.length))) end - machine:destroy() end local function list(tests) @@ -884,9 +869,7 @@ for _, test in ipairs(riscv_tests) do end end -local function run_host_and_uarch_machines(target, ctx, max_mcycle) - local host_machine = target.host - local uarch_machine = target.uarch +local function run_host_and_uarch_machines(host_machine, uarch_machine, ctx, max_mcycle) local host_cycles = host_machine:read_mcycle() local uarch_cycles = uarch_machine:read_mcycle() assert(host_cycles == uarch_cycles) @@ -947,27 +930,21 @@ if #selected_tests < 1 then error("no test selected") elseif command == "run" then failures = parallel.run(contexts, jobs, function(row) - local machine = build_machine(row.ram_image) + local machine = build_machine(row.ram_image) run_machine(machine, row, 2 * row.expected_cycles) check_and_print_result(machine, row) - machine:destroy() end) elseif command == "run_uarch" then failures = parallel.run(contexts, jobs, function(row) - local machine = build_machine(row.ram_image) + local machine = build_machine(row.ram_image) run_machine_with_uarch(machine, row, 2 * row.expected_cycles) check_and_print_result(machine, row) - machine:destroy() end) elseif command == "run_host_and_uarch" then failures = parallel.run(contexts, jobs, function(row) - local targets = { - host = build_machine(row.ram_image), - uarch = build_machine(row.ram_image), - } - run_host_and_uarch_machines(targets, row, 2 * row.expected_cycles) - targets.host:destroy() - targets.uarch:destroy() + local host_machine = build_machine(row.ram_image) + local uarch_machine = build_machine(row.ram_image) + run_host_and_uarch_machines(host_machine, uarch_machine, row, 2 * row.expected_cycles) end) elseif command == "hash" then hash(selected_tests) diff --git a/tests/lua/cmio-test.lua b/tests/lua/cmio-test.lua index 673673409..d2c45cc1c 100755 --- a/tests/lua/cmio-test.lua +++ b/tests/lua/cmio-test.lua @@ -19,6 +19,7 @@ local cartesi = require("cartesi") local test_util = require("cartesi.tests.util") local test_data = require("cartesi.tests.data") +local jsonrpc local function adjust_images_path(path) return string.gsub(path or ".", "/*$", "") .. "/" @@ -38,14 +39,9 @@ Usage: where options are: - --remote-address=
+ --remote-address=: run tests on a remote cartesi machine (when machine type is jsonrpc). -
is one of the following formats: - : - unix: - - can be a host name, IPv4 or IPv6 address. ]=], arg[0] )) @@ -106,21 +102,13 @@ end local machine_type = assert(arguments[1], "missing machine type") assert(machine_type == "local" or machine_type == "jsonrpc", "unknown machine type, should be 'local' or 'jsonrpc'") -local protocol +local to_shutdown if machine_type == "jsonrpc" then assert(remote_address ~= nil, "remote cartesi machine address is missing") - protocol = require("cartesi.jsonrpc") -end - -local function connect() - local remote = protocol.connect(remote_address) -- server will be shutdown when remote is collected - local version = - assert(remote.get_server_version(), "could not connect to remote cartesi machine at " .. remote_address) - return remote, version + jsonrpc = require("cartesi.jsonrpc") + to_shutdown = jsonrpc.connect_server(remote_address):set_cleanup_call(jsonrpc.SHUTDOWN) end -local remote - -- There is no UINT64_MAX in Lua, so we have to use the signed representation local MAX_MCYCLE = -1 @@ -133,10 +121,8 @@ local function load_machine(name) skip_root_hash_store = true, } if machine_type ~= "local" then - if not remote then - remote = connect() - end - return assert(remote.machine(MACHINES_DIR .. name, runtime)) + local jsonrpc_machine = assert(jsonrpc.connect_server(remote_address)) + return assert(jsonrpc_machine(MACHINES_DIR .. name, runtime):set_cleanup_call(jsonrpc.SHUTDOWN)) else return assert(cartesi.machine(MACHINES_DIR .. name, runtime)) end diff --git a/tests/lua/log-with-mtime-transition.lua b/tests/lua/log-with-mtime-transition.lua index a766ba42a..ba402519d 100755 --- a/tests/lua/log-with-mtime-transition.lua +++ b/tests/lua/log-with-mtime-transition.lua @@ -15,5 +15,5 @@ local machine = cartesi.machine(config) local old_hash = machine:get_root_hash() local access_log = machine:log_step_uarch() local new_hash = machine:get_root_hash() -cartesi.machine.verify_step_uarch(old_hash, access_log, new_hash, {}) +cartesi.machine:verify_step_uarch(old_hash, access_log, new_hash, {}) print("ok") diff --git a/tests/lua/machine-bind.lua b/tests/lua/machine-bind.lua index ae4fa585a..a22b364d3 100755 --- a/tests/lua/machine-bind.lua +++ b/tests/lua/machine-bind.lua @@ -19,10 +19,10 @@ local cartesi = require("cartesi") local util = require("cartesi.util") local test_util = require("cartesi.tests.util") +local jsonrpc local remote_address local test_path = "./" -local cleanup = {} local concurrency_update_merkle_tree = util.parse_number(os.getenv("CARTESI_CONCURRENCY_UPDATE_MERKLE_TREE")) or 0 local lua_cmd = arg[-1] .. " -e " @@ -41,7 +41,7 @@ Usage: where options are: - --remote-address=
+ --remote-address=: run tests on a remote cartesi machine (when machine type is jsonrpc). --test-path= @@ -60,11 +60,6 @@ where options are: when omitted or defined as 0, the number of hardware threads is used if it can be identified or else a single thread is used. -
is one of the following formats: - : - unix: - - can be a host name, IPv4 or IPv6 address. ]=], arg[0] )) @@ -246,22 +241,14 @@ end local machine_type = assert(arguments[1], "missing machine type") assert(machine_type == "local" or machine_type == "jsonrpc", "unknown machine type, should be 'local' or 'jsonrpc'") -local protocol +local to_shutdown if machine_type == "jsonrpc" then assert(remote_address ~= nil, "remote cartesi machine address is missing") assert(test_path ~= nil, "test path must be provided and must be working directory of remote cartesi machine") - protocol = require("cartesi.jsonrpc") -end - -local function connect() - local remote = protocol.connect(remote_address) -- server will be shutdown when remote is collected - local version = - assert(remote.get_server_version(), "could not connect to remote cartesi machine at " .. remote_address) - return remote, version + jsonrpc = require("cartesi.jsonrpc") + to_shutdown = jsonrpc.connect_server(remote_address):set_cleanup_call(jsonrpc.SHUTDOWN) end -local remote - local function build_machine_config(config_options) if not config_options then config_options = {} @@ -294,10 +281,8 @@ local function build_machine(type, config_options) local config, runtime = build_machine_config(config_options) local new_machine if type ~= "local" then - if not remote then - remote = connect() - end - new_machine = assert(remote.machine(config, runtime)) + local jsonrpc_machine = assert(jsonrpc.connect_server(remote_address)) + new_machine = assert(jsonrpc_machine(config, runtime):set_cleanup_call(jsonrpc.SHUTDOWN)) else new_machine = assert(cartesi.machine(config, runtime)) end @@ -349,44 +334,29 @@ do_test("should provide proof for values in registers", function(machine) end) print("\n\ntesting get_reg_address function binding") -do_test("should return address value for registers", function() - local module = cartesi - if machine_type ~= "local" then - if not remote then - remote = connect() - end - module = remote - end +do_test("should return address value for registers", function(machine) -- Check register address for k, v in pairs(cpu_reg_addr) do - local u = module.machine.get_reg_address(k) + local u = machine:get_reg_address(k) assert(u == v, "invalid return for " .. v) end end) print("\n\ntesting get x address function binding") -do_test("should return address value for x registers", function() - local module = cartesi - if machine_type ~= "local" then - if not remote then - remote = connect() - end - module = remote - end +do_test("should return address value for x registers", function(machine) -- Check x address for i = 0, 31 do - assert(module.machine.get_reg_address("x" .. i) == SHADOW_BASE + i * 8, "invalid return for x" .. i) + assert(machine:get_reg_address("x" .. i) == SHADOW_BASE + i * 8, "invalid return for x" .. i) end end) print("\n\ntesting get x uarch_address function binding") -do_test("should return address value for uarch x registers", function() +do_test("should return address value for uarch x registers", function(machine) local SHADOW_UARCH_XBASE = cartesi.UARCH_SHADOW_START_ADDRESS + 24 - local module = cartesi -- Check x address for i = 0, 31 do assert( - module.machine.get_reg_address("uarch_x" .. i) == SHADOW_UARCH_XBASE + i * 8, + machine:get_reg_address("uarch_x" .. i) == SHADOW_UARCH_XBASE + i * 8, "invalid return for uarch x" .. i ) end @@ -450,15 +420,8 @@ local function test_config(config) end print("\n\ntesting get_default_config function binding") -do_test("should return default machine config", function() - local module = cartesi - if machine_type ~= "local" then - if not remote then - remote = connect() - end - module = remote - end - test_config(module.machine.get_default_config()) +do_test("should return default machine config", function(machine) + test_config(machine:get_default_config()) end) print("\n\n test verifying integrity of the merkle tree") @@ -701,39 +664,25 @@ end) print("\n\ntesting step and verification") do_test("machine step should pass verifications", function(machine) - local module = cartesi - if machine_type ~= "local" then - if not remote then - remote = connect() - end - module = remote - end local initial_hash = machine:get_root_hash() local log = machine:log_step_uarch(cartesi.ACCESS_LOG_TYPE_ANNOTATIONS) local final_hash = machine:get_root_hash() - module.machine.verify_step_uarch(initial_hash, log, final_hash) + machine:verify_step_uarch(initial_hash, log, final_hash) end) print("\n\ntesting step and verification") do_test("Step log must contain conssitent data hashes", function(machine) local wrong_hash = string.rep("\0", cartesi.HASH_SIZE) - local module = cartesi - if machine_type ~= "local" then - if not remote then - remote = connect() - end - module = remote - end local initial_hash = machine:get_root_hash() local log = machine:log_step_uarch() local final_hash = machine:get_root_hash() - module.machine.verify_step_uarch(initial_hash, log, final_hash) + machine:verify_step_uarch(initial_hash, log, final_hash) local read_access = log.accesses[1] assert(read_access.type == "read") local read_hash = read_access.read_hash -- ensure that verification fails with wrong read hash read_access.read_hash = wrong_hash - local _, err = pcall(module.machine.verify_step_uarch, initial_hash, log, final_hash) + local _, err = pcall(machine.verify_step_uarch, machine, initial_hash, log, final_hash) assert(err:match("logged read data of uarch.uarch_cycle data does not hash to the logged read hash at 1st access")) read_access.read_hash = read_hash -- restore correct value @@ -742,24 +691,17 @@ do_test("Step log must contain conssitent data hashes", function(machine) assert(write_access.type == "write") read_hash = write_access.read_hash write_access.read_hash = wrong_hash - _, err = pcall(module.machine.verify_step_uarch, initial_hash, log, final_hash) + _, err = pcall(machine.verify_step_uarch, machine, initial_hash, log, final_hash) assert(err:match("logged read data of uarch.cycle does not hash to the logged read hash at 8th access")) write_access.read_hash = read_hash -- restore correct value -- ensure that verification fails with wrong written hash write_access.written_hash = wrong_hash - _, err = pcall(module.machine.verify_step_uarch, initial_hash, log, final_hash) + _, err = pcall(machine.verify_step_uarch, machine, initial_hash, log, final_hash) assert(err:match("logged written data of uarch.cycle does not hash to the logged written hash at 8th access")) end) do_test("step when uarch cycle is max", function(machine) - local module = cartesi - if machine_type ~= "local" then - if not remote then - remote = connect() - end - module = remote - end machine:write_reg("uarch_cycle", MAX_UARCH_CYCLE) assert(machine:read_uarch_cycle() == MAX_UARCH_CYCLE) local initial_hash = machine:get_root_hash() @@ -767,7 +709,7 @@ do_test("step when uarch cycle is max", function(machine) assert(machine:read_uarch_cycle() == MAX_UARCH_CYCLE) local final_hash = machine:get_root_hash() assert(final_hash == initial_hash) - module.machine.verify_step_uarch(initial_hash, log, final_hash) + machine:verify_step_uarch(initial_hash, log, final_hash) end) local uarch_proof_step_program = { @@ -948,21 +890,17 @@ end test_util.make_do_test(build_machine, machine_type, { uarch = test_reset_uarch_config })( "Testing verify_reset_uarch", function(machine) - local module = cartesi - if machine_type ~= "local" then - module = remote - end local initial_hash = machine:get_root_hash() local log = machine:log_reset_uarch(cartesi.ACCESS_LOG_TYPE_ANNOTATIONS) local final_hash = machine:get_root_hash() -- verify happy path - module.machine.verify_reset_uarch(initial_hash, log, final_hash) + machine:verify_reset_uarch(initial_hash, log, final_hash) -- verifying incorrect initial hash local wrong_hash = string.rep("0", cartesi.HASH_SIZE) - local _, err = pcall(module.machine.verify_reset_uarch, wrong_hash, log, final_hash) + local _, err = pcall(machine.verify_reset_uarch, machine, wrong_hash, log, final_hash) assert(err:match("Mismatch in root hash of 1st access")) -- verifying incorrect final hash - _, err = pcall(module.machine.verify_reset_uarch, initial_hash, log, wrong_hash) + _, err = pcall(machine.verify_reset_uarch, machine, initial_hash, log, wrong_hash) assert(err:match("mismatch in root hash after replay")) end ) @@ -970,14 +908,10 @@ test_util.make_do_test(build_machine, machine_type, { uarch = test_reset_uarch_c test_util.make_do_test(build_machine, machine_type, { uarch = test_reset_uarch_config })( "Testing verify_reset_uarch", function(machine) - local module = cartesi - if machine_type ~= "local" then - module = remote - end local initial_hash = machine:get_root_hash() local log = machine:log_reset_uarch() local final_hash = machine:get_root_hash() - module.machine.verify_reset_uarch(initial_hash, log, final_hash) + machine:verify_reset_uarch(initial_hash, log, final_hash) end ) @@ -1016,10 +950,6 @@ test_util.make_do_test(build_machine, machine_type, { uarch = test_reset_uarch_c test_util.make_do_test(build_machine, machine_type, { uarch = test_reset_uarch_config })( "Log uarch reset with large_data option set must have consistent read and written data", function(machine) - local module = cartesi - if machine_type ~= "local" then - module = remote - end -- reset uarch and get log local initial_hash = machine:get_root_hash() local log = machine:log_reset_uarch(cartesi.ACCESS_LOG_TYPE_ANNOTATIONS | cartesi.ACCESS_LOG_TYPE_LARGE_DATA) @@ -1030,30 +960,23 @@ test_util.make_do_test(build_machine, machine_type, { uarch = test_reset_uarch_c assert(access.read ~= nil, "read data should not be nil") assert(access.written ~= nil, "written data should not be nil") -- verify returned log - module.machine.verify_reset_uarch(initial_hash, log, final_hash) + machine:verify_reset_uarch(initial_hash, log, final_hash) -- save logged read and written data local original_read = access.read -- tamper with read data to produce a hash mismatch access.read = "X" .. access.read:sub(2) - local _, err = pcall(module.machine.verify_reset_uarch, initial_hash, log, final_hash) + local _, err = pcall(machine.verify_reset_uarch, machine, initial_hash, log, final_hash) assert(err:match("hash of read data and read hash at 1st access does not match read hash")) -- restore correct read access.read = original_read -- change written data to produce a hash mismatch access.written = "X" .. access.written:sub(2) - _, err = pcall(module.machine.verify_reset_uarch, initial_hash, log, final_hash) + _, err = pcall(machine.verify_reset_uarch, machine, initial_hash, log, final_hash) assert(err:match("written hash and written data mismatch at 1st access")) end ) do_test("Test unhappy paths of verify_reset_uarch", function(machine) - local module = cartesi - if machine_type ~= "local" then - if not remote then - remote = connect() - end - module = remote - end local bad_hash = string.rep("\0", cartesi.HASH_SIZE) local function assert_error(expected_error, callback) machine:reset_uarch() @@ -1061,7 +984,7 @@ do_test("Test unhappy paths of verify_reset_uarch", function(machine) local log = machine:log_reset_uarch() local final_hash = machine:get_root_hash() callback(log) - local _, err = pcall(module.machine.verify_reset_uarch, initial_hash, log, final_hash) + local _, err = pcall(machine.verify_reset_uarch, machine, initial_hash, log, final_hash) assert( err:match(expected_error), 'Error text "' .. err .. '" does not match expected "' .. expected_error .. '"' @@ -1102,13 +1025,6 @@ do_test("Test unhappy paths of verify_reset_uarch", function(machine) end) do_test("Test unhappy paths of verify_step_uarch", function(machine) - local module = cartesi - if machine_type ~= "local" then - if not remote then - remote = connect() - end - module = remote - end local bad_hash = string.rep("\0", cartesi.HASH_SIZE) local function assert_error(expected_error, callback) machine:reset_uarch() @@ -1116,7 +1032,7 @@ do_test("Test unhappy paths of verify_step_uarch", function(machine) local log = machine:log_step_uarch() local final_hash = machine:get_root_hash() callback(log) - local _, err = pcall(module.machine.verify_step_uarch, initial_hash, log, final_hash) + local _, err = pcall(machine.verify_step_uarch, machine, initial_hash, log, final_hash) assert( err:match(expected_error), 'Error text "' .. err .. '" does not match expected "' .. expected_error .. '"' @@ -1295,13 +1211,6 @@ local function test_send_cmio_input_with_different_arguments() function(machine) local log_type = (annotations and cartesi.ACCESS_LOG_TYPE_ANNOTATIONS or 0) | (large_data and cartesi.ACCESS_LOG_TYPE_LARGE_DATA or 0) - local module = cartesi - if machine_type ~= "local" then - if not remote then - remote = connect() - end - module = remote - end assert_before_cmio_response_sent(machine) local root_hash_before = machine:get_root_hash() local log = machine:log_send_cmio_response(reason, data, log_type) @@ -1312,7 +1221,7 @@ local function test_send_cmio_input_with_different_arguments() assert(#accesses == 5) assert_access(accesses, 1, { type = "read", - address = module.machine.get_reg_address("iflags"), + address = machine:get_reg_address("iflags"), log2_size = 3, }) assert_access(accesses, 2, { @@ -1326,21 +1235,21 @@ local function test_send_cmio_input_with_different_arguments() }) assert_access(accesses, 3, { type = "write", - address = module.machine.get_reg_address("htif_fromhost"), + address = machine:get_reg_address("htif_fromhost"), log2_size = 3, }) assert_access(accesses, 4, { type = "read", - address = module.machine.get_reg_address("iflags"), + address = machine:get_reg_address("iflags"), log2_size = 3, }) assert_access(accesses, 5, { type = "write", - address = module.machine.get_reg_address("iflags"), + address = machine:get_reg_address("iflags"), log2_size = 3, }) -- ask machine to verify state transitions - module.machine.verify_send_cmio_response( + machine:verify_send_cmio_response( reason, data, root_hash_before, @@ -1436,13 +1345,6 @@ do_test("send_cmio_response with different data sizes", function(machine) end) do_test("send_cmio_response of zero bytes", function(machine) - local module = cartesi - if machine_type ~= "local" then - if not remote then - remote = connect() - end - module = remote - end local rx_buffer_size = 1 << cartesi.PMA_CMIO_RX_BUFFER_LOG2_SIZE local initial_rx_buffer = string.rep("x", rx_buffer_size) machine:write_memory(cartesi.PMA_CMIO_RX_BUFFER_START, initial_rx_buffer) @@ -1460,7 +1362,7 @@ do_test("send_cmio_response of zero bytes", function(machine) local log = machine:log_send_cmio_response(reason, data) assert(#log.accesses == 4, "log should have 4 accesses") local hash_after = machine:get_root_hash() - module.machine.verify_send_cmio_response(reason, data, hash_before, log, hash_after) + machine:verify_send_cmio_response(reason, data, hash_before, log, hash_after) end) local function test_cmio_buffers_backed_by_files() diff --git a/tests/lua/machine-test.lua b/tests/lua/machine-test.lua index fa14e4ba1..bd910e037 100755 --- a/tests/lua/machine-test.lua +++ b/tests/lua/machine-test.lua @@ -18,6 +18,7 @@ local cartesi = require("cartesi") local test_util = require("cartesi.tests.util") +local jsonrpc -- Note: for jsonrpc machine test to work, jsonrpc-remote-cartesi-machine must -- run on the same computer and jsonrpc-remote-cartesi-machine execution path @@ -40,7 +41,7 @@ Usage: where options are: - --remote-address=
+ --remote-address=: run tests on a remote cartesi machine (when machine type is jsonrpc). --test-path= @@ -48,11 +49,6 @@ where options are: working directory of jsonrpc-remote-cartesi-machine and must be locally readable (default: "./") -
is one of the following formats: - : - unix: - - can be a host name, IPv4 or IPv6 address. ]=], arg[0] )) @@ -125,21 +121,14 @@ end local machine_type = assert(arguments[1], "missing machine type") assert(machine_type == "local" or machine_type == "jsonrpc", "unknown machine type, should be 'local' or 'jsonrpc'") -local protocol +local to_shutdown if machine_type == "jsonrpc" then assert(remote_address ~= nil, "remote cartesi machine address is missing") assert(test_path ~= nil, "test path must be provided and must be working directory of remote cartesi machine") - protocol = require("cartesi.jsonrpc") -end - -local function connect() - local remote = protocol.connect(remote_address) -- server will be shutdown when connection is collected - local version = - assert(remote.get_server_version(), "could not connect to remote cartesi machine at " .. remote_address) - return remote, version + jsonrpc = require("cartesi.jsonrpc") + to_shutdown = jsonrpc.connect_server(remote_address):set_cleanup_call(jsonrpc.SHUTDOWN) end -local remote local function build_machine(type, config, runtime_config) config = config or { ram = { length = 1 << 20 }, @@ -149,16 +138,12 @@ local function build_machine(type, config, runtime_config) update_merkle_tree = 0, }, } - local new_machine if type ~= "local" then - if not remote then - remote = connect() - end - new_machine = assert(remote.machine(config, runtime_config)) + local jsonrpc_machine = assert(jsonrpc.connect_server(remote_address)) + return assert(jsonrpc_machine(config, runtime_config):set_cleanup_call(jsonrpc.SHUTDOWN)) else - new_machine = assert(cartesi.machine(config, runtime_config)) + return assert(cartesi.machine(config, runtime_config)) end - return new_machine end local do_test = test_util.make_do_test(build_machine, machine_type) @@ -291,7 +276,6 @@ if machine_type == "local" then local soft_yield_insn = sraiw(0, 31, 7) machine:write_memory(machine:read_reg("pc"), string.pack(" 0) local child = fork_tree(child_address, clone_x(x), depth + 1) children[#children + 1] = child @@ -154,7 +151,7 @@ end local function check_tree(root) pre_order(root, function(node, depth) - local machine = node.stub.get_machine() + local machine = node.machine local x = node.x io.write(string.rep(" ", depth), "{", table.concat(node.x, ","), "}\n") for i = 1, 31 do @@ -167,7 +164,7 @@ end local function kill_tree(root) pre_order(root, function(node) - node.stub.shutdown_server() + node.machine:shutdown_server() end) end diff --git a/tests/misc/test-machine-c-api.cpp b/tests/misc/test-machine-c-api.cpp index f9ca04a03..23d64cbe0 100644 --- a/tests/misc/test-machine-c-api.cpp +++ b/tests/misc/test-machine-c-api.cpp @@ -25,6 +25,7 @@ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" #include #include #include @@ -55,12 +56,12 @@ #define BOOST_FIXTURE_TEST_CASE_NOLINT(...) BOOST_FIXTURE_TEST_CASE(__VA_ARGS__) BOOST_AUTO_TEST_CASE_NOLINT(delete_machine_null_test) { - BOOST_CHECK_NO_THROW(cm_destroy(nullptr)); + BOOST_CHECK_NO_THROW(cm_delete(nullptr)); } BOOST_AUTO_TEST_CASE_NOLINT(get_default_machine_config_basic_test) { const char *config{}; - cm_error error_code = cm_get_default_config(&config); + cm_error error_code = cm_get_default_config(nullptr, &config); BOOST_CHECK_EQUAL(error_code, CM_ERROR_OK); BOOST_CHECK_EQUAL(std::string(""), std::string(cm_get_last_error_message())); BOOST_TEST_CHECK(config != nullptr); @@ -70,7 +71,7 @@ class default_machine_fixture { public: default_machine_fixture() { const char *config{}; - cm_get_default_config(&config); + cm_get_default_config(nullptr, &config); _default_machine_config = config; } @@ -87,7 +88,7 @@ class default_machine_fixture { }; BOOST_FIXTURE_TEST_CASE_NOLINT(load_machine_unknown_dir_test, default_machine_fixture) { - cm_error error_code = cm_load("/unknown_dir", nullptr, &_machine); + cm_error error_code = cm_load_new("/unknown_dir", nullptr, &_machine); BOOST_CHECK_EQUAL(error_code, CM_ERROR_SYSTEM_ERROR); std::string result = cm_get_last_error_message(); @@ -95,7 +96,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(load_machine_unknown_dir_test, default_machine_fi } BOOST_FIXTURE_TEST_CASE_NOLINT(load_machine_null_path_test, default_machine_fixture) { - cm_error error_code = cm_load(nullptr, nullptr, &_machine); + cm_error error_code = cm_load_new(nullptr, nullptr, &_machine); BOOST_CHECK_EQUAL(error_code, CM_ERROR_INVALID_ARGUMENT); std::string result = cm_get_last_error_message(); @@ -103,7 +104,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(load_machine_null_path_test, default_machine_fixt } BOOST_FIXTURE_TEST_CASE_NOLINT(create_machine_null_config_test, default_machine_fixture) { - cm_error error_code = cm_create(nullptr, nullptr, &_machine); + cm_error error_code = cm_create_new(nullptr, nullptr, &_machine); BOOST_CHECK_EQUAL(error_code, CM_ERROR_INVALID_ARGUMENT); std::string result = cm_get_last_error_message(); std::string origin("invalid machine configuration"); @@ -111,7 +112,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(create_machine_null_config_test, default_machine_ } BOOST_FIXTURE_TEST_CASE_NOLINT(create_machine_default_machine_test, default_machine_fixture) { - cm_error error_code = cm_create(_default_machine_config.c_str(), nullptr, &_machine); + cm_error error_code = cm_create_new(_default_machine_config.c_str(), nullptr, &_machine); BOOST_CHECK_EQUAL(error_code, CM_ERROR_INVALID_ARGUMENT); BOOST_REQUIRE_EQUAL(std::string(cm_get_last_error_message()), std::string("RAM length cannot be zero")); } @@ -121,7 +122,7 @@ class incomplete_machine_fixture : public default_machine_fixture { public: incomplete_machine_fixture() : _machine_config{} { const char *config{}; - cm_get_default_config(&config); + cm_get_default_config(nullptr, &config); _machine_config = nlohmann::json::parse(config); _machine_config["ram"]["length"] = 1 << 20; } @@ -136,7 +137,7 @@ class incomplete_machine_fixture : public default_machine_fixture { }; BOOST_FIXTURE_TEST_CASE_NOLINT(create_machine_null_machine_test, incomplete_machine_fixture) { - cm_error error_code = cm_create(_machine_config.dump().c_str(), nullptr, nullptr); + cm_error error_code = cm_create_new(_machine_config.dump().c_str(), nullptr, nullptr); BOOST_CHECK_EQUAL(error_code, CM_ERROR_INVALID_ARGUMENT); std::string result = cm_get_last_error_message(); @@ -150,7 +151,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(replace_memory_range_pma_overlapping_test, incomp nlohmann::json{"shared", true}}, nlohmann::json{nlohmann::json{"start", 0x7ffffffffff000}, nlohmann::json{"length", 0x2000}, nlohmann::json{"shared", true}}}; - cm_error error_code = cm_create(_machine_config.dump().c_str(), nullptr, &_machine); + cm_error error_code = cm_create_new(_machine_config.dump().c_str(), nullptr, &_machine); BOOST_CHECK_EQUAL(error_code, CM_ERROR_INVALID_ARGUMENT); std::string result = cm_get_last_error_message(); @@ -174,7 +175,7 @@ class machine_flash_simple_fixture : public incomplete_machine_fixture { BOOST_FIXTURE_TEST_CASE_NOLINT(replace_memory_range_invalid_alignment_test, machine_flash_simple_fixture) { _machine_config["flash_drive"][0]["start"] = 0x80000000000000 - 1; - cm_error error_code = cm_create(_machine_config.dump().c_str(), nullptr, &_machine); + cm_error error_code = cm_create_new(_machine_config.dump().c_str(), nullptr, &_machine); BOOST_CHECK_EQUAL(error_code, CM_ERROR_INVALID_ARGUMENT); std::string result = cm_get_last_error_message(); @@ -186,7 +187,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(replace_memory_range_not_addressable_test, machin _machine_config["flash_drive"][0]["start"] = 0x100000000000000 - 0x3c00000 + 4096; _machine_config["flash_drive"][0]["length"] = 0x3c00000; - cm_error error_code = cm_create(_machine_config.dump().c_str(), nullptr, &_machine); + cm_error error_code = cm_create_new(_machine_config.dump().c_str(), nullptr, &_machine); BOOST_CHECK_EQUAL(error_code, CM_ERROR_INVALID_ARGUMENT); std::string result = cm_get_last_error_message(); @@ -198,11 +199,11 @@ class ordinary_machine_fixture : public incomplete_machine_fixture { public: ordinary_machine_fixture() { _machine_dir_path = (std::filesystem::temp_directory_path() / "661b6096c377cdc07756df488059f4407c8f4").string(); - cm_create(_machine_config.dump().c_str(), nullptr, &_machine); + cm_create_new(_machine_config.dump().c_str(), nullptr, &_machine); } ~ordinary_machine_fixture() { std::filesystem::remove_all(_machine_dir_path); - cm_destroy(_machine); + cm_delete(_machine); } ordinary_machine_fixture(const ordinary_machine_fixture &other) = delete; @@ -261,11 +262,11 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(load_machine_invalid_config_version_test, seriali expected_err << "expected \"archive_version\" " << v << " (got " << v + 1 << ")"; cm_machine *restored_machine{}; - cm_error error_code = cm_load(_machine_config_path.c_str(), nullptr, &restored_machine); + cm_error error_code = cm_load_new(_machine_config_path.c_str(), nullptr, &restored_machine); BOOST_CHECK_EQUAL(error_code, CM_ERROR_RUNTIME_ERROR); BOOST_CHECK_EQUAL(std::string(cm_get_last_error_message()), expected_err.str()); - cm_destroy(restored_machine); + cm_delete(restored_machine); } class store_file_fixture : public ordinary_machine_fixture { @@ -329,7 +330,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(load_machine_null_machine_test, ordinary_machine_ cm_error error_code = cm_store(_machine, _machine_dir_path.c_str()); BOOST_REQUIRE_EQUAL(error_code, CM_ERROR_OK); - error_code = cm_load(_machine_dir_path.c_str(), nullptr, nullptr); + error_code = cm_load_new(_machine_dir_path.c_str(), nullptr, nullptr); BOOST_REQUIRE_EQUAL(error_code, CM_ERROR_INVALID_ARGUMENT); } @@ -339,7 +340,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(serde_complex_test, ordinary_machine_fixture) { BOOST_CHECK_EQUAL(std::string(""), std::string(cm_get_last_error_message())); cm_machine *restored_machine{}; - error_code = cm_load(_machine_dir_path.c_str(), nullptr, &restored_machine); + error_code = cm_load_new(_machine_dir_path.c_str(), nullptr, &restored_machine); BOOST_CHECK_EQUAL(error_code, CM_ERROR_OK); BOOST_CHECK_EQUAL(std::string(""), std::string(cm_get_last_error_message())); @@ -353,7 +354,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(serde_complex_test, ordinary_machine_fixture) { BOOST_REQUIRE_EQUAL(std::string(cm_get_last_error_message()), std::string("")); BOOST_CHECK_EQUAL(0, memcmp(origin_hash, restored_hash, sizeof(cm_hash))); - cm_destroy(restored_machine); + cm_delete(restored_machine); } BOOST_AUTO_TEST_CASE_NOLINT(get_root_hash_null_machine_test) { @@ -471,7 +472,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(read_word_null_output_test, default_machine_fixtu BOOST_FIXTURE_TEST_CASE_NOLINT(read_word_basic_test, ordinary_machine_fixture) { uint64_t word_value = 0; uint64_t pc_addr{}; - BOOST_CHECK_EQUAL(cm_get_reg_address(CM_REG_PC, &pc_addr), CM_ERROR_OK); + BOOST_CHECK_EQUAL(cm_get_reg_address(_machine, CM_REG_PC, &pc_addr), CM_ERROR_OK); cm_error error_code = cm_read_word(_machine, pc_addr, &word_value); BOOST_CHECK_EQUAL(error_code, CM_ERROR_OK); BOOST_CHECK_EQUAL(std::string(""), std::string(cm_get_last_error_message())); @@ -893,7 +894,7 @@ class flash_drive_machine_fixture : public machine_flash_simple_fixture { _flash_file{"/tmp/data.bin"}, _flash_data{"test data 1234567890"} { _machine_dir_path = (std::filesystem::temp_directory_path() / "661b6096c377cdc07756df488059f4407c8f4").string(); - cm_create(_machine_config.dump().c_str(), nullptr, &_machine); + cm_create_new(_machine_config.dump().c_str(), nullptr, &_machine); std::ofstream flash_stream(_flash_file); flash_stream << _flash_data; flash_stream.close(); @@ -901,7 +902,7 @@ class flash_drive_machine_fixture : public machine_flash_simple_fixture { } ~flash_drive_machine_fixture() { - cm_destroy(_machine); + cm_delete(_machine); std::filesystem::remove_all(_machine_dir_path); std::filesystem::remove(_flash_file); } @@ -989,42 +990,16 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(replace_memory_range_basic_test, flash_drive_mach BOOST_CHECK_EQUAL(_flash_data, read_string); } -BOOST_AUTO_TEST_CASE_NOLINT(destroy_null_machine_test) { - cm_destroy(nullptr); +BOOST_AUTO_TEST_CASE_NOLINT(delete_null_machine_test) { + cm_delete(nullptr); } -BOOST_FIXTURE_TEST_CASE_NOLINT(destroy_basic_test, ordinary_machine_fixture) { - cm_destroy(_machine); +BOOST_FIXTURE_TEST_CASE_NOLINT(delete_basic_test, ordinary_machine_fixture) { + cm_delete(_machine); _machine = nullptr; BOOST_CHECK_EQUAL(std::string(""), std::string(cm_get_last_error_message())); } -BOOST_AUTO_TEST_CASE_NOLINT(snapshot_null_machine_test) { - cm_error error_code = cm_snapshot(nullptr); - BOOST_CHECK_EQUAL(error_code, CM_ERROR_INVALID_ARGUMENT); -} - -BOOST_FIXTURE_TEST_CASE_NOLINT(snapshot_basic_test, ordinary_machine_fixture) { - cm_error error_code = cm_snapshot(_machine); - std::string result = cm_get_last_error_message(); - std::string origin("snapshot is not supported"); - BOOST_CHECK_EQUAL(error_code, CM_ERROR_RUNTIME_ERROR); - BOOST_CHECK_EQUAL(origin, result); -} - -BOOST_AUTO_TEST_CASE_NOLINT(rollback_null_machine_test) { - cm_error error_code = cm_rollback(nullptr); - BOOST_CHECK_EQUAL(error_code, CM_ERROR_INVALID_ARGUMENT); -} - -BOOST_FIXTURE_TEST_CASE_NOLINT(rollback_basic_test, ordinary_machine_fixture) { - cm_error error_code = cm_rollback(_machine); - std::string result = cm_get_last_error_message(); - std::string origin("rollback is not supported"); - BOOST_CHECK_EQUAL(error_code, CM_ERROR_RUNTIME_ERROR); - BOOST_CHECK_EQUAL(origin, result); -} - BOOST_FIXTURE_TEST_CASE_NOLINT(read_write_x_basic_test, ordinary_machine_fixture) { uint64_t x_origin = 42; uint64_t x_read{}; @@ -1038,7 +1013,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(read_write_x_basic_test, ordinary_machine_fixture BOOST_CHECK_EQUAL(x_origin, x_read); uint64_t x2_addr{}; - BOOST_CHECK_EQUAL(cm_get_reg_address(CM_REG_X2, &x2_addr), CM_ERROR_OK); + BOOST_CHECK_EQUAL(cm_get_reg_address(_machine, CM_REG_X2, &x2_addr), CM_ERROR_OK); BOOST_CHECK_EQUAL(static_cast(0x10), x2_addr); } @@ -1055,7 +1030,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(read_write_f_basic_test, ordinary_machine_fixture BOOST_CHECK_EQUAL(f_origin, f_read); uint64_t f2_addr{}; - BOOST_CHECK_EQUAL(cm_get_reg_address(CM_REG_F2, &f2_addr), CM_ERROR_OK); + BOOST_CHECK_EQUAL(cm_get_reg_address(_machine, CM_REG_F2, &f2_addr), CM_ERROR_OK); BOOST_CHECK_EQUAL(static_cast(0x110), f2_addr); } @@ -1072,7 +1047,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(read_write_uarch_x_basic_test, ordinary_machine_f BOOST_CHECK_EQUAL(uarch_x_origin, uarch_x_read); uint64_t uarch_x2_addr{}; - BOOST_CHECK_EQUAL(cm_get_reg_address(CM_REG_UARCH_X2, &uarch_x2_addr), CM_ERROR_OK); + BOOST_CHECK_EQUAL(cm_get_reg_address(_machine, CM_REG_UARCH_X2, &uarch_x2_addr), CM_ERROR_OK); BOOST_CHECK_EQUAL(static_cast(cartesi::PMA_SHADOW_UARCH_STATE_START + 40), uarch_x2_addr); } @@ -1106,7 +1081,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(read_write_reg_basic_test, ordinary_machine_fixtu BOOST_CHECK_EQUAL(reg_origin, reg_read); uint64_t pc_addr{}; - BOOST_CHECK_EQUAL(cm_get_reg_address(CM_REG_PC, &pc_addr), CM_ERROR_OK); + BOOST_CHECK_EQUAL(cm_get_reg_address(_machine, CM_REG_PC, &pc_addr), CM_ERROR_OK); BOOST_CHECK_EQUAL(static_cast(0x200), pc_addr); } @@ -1130,7 +1105,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(verify_merkle_tree_basic_test, ordinary_machine_f } BOOST_FIXTURE_TEST_CASE_NOLINT(verify_step_uarch_log_null_log_test, default_machine_fixture) { - cm_error error_code = cm_verify_step_uarch(nullptr, nullptr, nullptr); + cm_error error_code = cm_verify_step_uarch(nullptr, nullptr, nullptr, nullptr); BOOST_CHECK_EQUAL(error_code, CM_ERROR_INVALID_ARGUMENT); std::string result = cm_get_last_error_message(); @@ -1154,10 +1129,10 @@ class access_log_machine_fixture : public incomplete_machine_fixture { of.close(); _machine_config["uarch"]["ram"]["image_filename"] = _uarch_ram_path; - cm_create(_machine_config.dump().c_str(), nullptr, &_machine); + cm_create_new(_machine_config.dump().c_str(), nullptr, &_machine); } ~access_log_machine_fixture() { - cm_destroy(_machine); + cm_delete(_machine); std::filesystem::remove_all(_machine_dir_path); std::filesystem::remove_all(_uarch_ram_path); } @@ -1190,7 +1165,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(verify_step_uarch_null_hash0_test, access_log_mac BOOST_CHECK_EQUAL(std::string(""), std::string(cm_get_last_error_message())); cm_hash hash1; - error_code = cm_verify_step_uarch(nullptr, _access_log, &hash1); + error_code = cm_verify_step_uarch(nullptr, nullptr, _access_log, &hash1); BOOST_CHECK_EQUAL(error_code, CM_ERROR_INVALID_ARGUMENT); std::string result = cm_get_last_error_message(); @@ -1204,7 +1179,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(verify_step_uarch_null_hash1_test, access_log_mac BOOST_CHECK_EQUAL(std::string(""), std::string(cm_get_last_error_message())); cm_hash hash0; - error_code = cm_verify_step_uarch(&hash0, _access_log, nullptr); + error_code = cm_verify_step_uarch(nullptr, &hash0, _access_log, nullptr); BOOST_CHECK_EQUAL(error_code, CM_ERROR_INVALID_ARGUMENT); std::string result = cm_get_last_error_message(); @@ -1215,7 +1190,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(verify_step_uarch_null_hash1_test, access_log_mac BOOST_FIXTURE_TEST_CASE_NOLINT(verify_step_uarch_null_access_log_test, access_log_machine_fixture) { cm_hash hash0; cm_hash hash1; - cm_error error_code = cm_verify_step_uarch(&hash0, nullptr, &hash1); + cm_error error_code = cm_verify_step_uarch(nullptr, &hash0, nullptr, &hash1); BOOST_CHECK_EQUAL(error_code, CM_ERROR_INVALID_ARGUMENT); std::string result = cm_get_last_error_message(); @@ -1255,7 +1230,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(log_step_uarch_until_halt, access_log_machine_fix error_code = cm_get_root_hash(_machine, &hash1); BOOST_REQUIRE_EQUAL(error_code, CM_ERROR_OK); // verify - error_code = cm_verify_step_uarch(&hash0, _access_log, &hash1); + error_code = cm_verify_step_uarch(nullptr, &hash0, _access_log, &hash1); BOOST_CHECK_EQUAL(error_code, CM_ERROR_OK); // step 2 @@ -1265,7 +1240,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(log_step_uarch_until_halt, access_log_machine_fix error_code = cm_get_root_hash(_machine, &hash2); BOOST_REQUIRE_EQUAL(error_code, CM_ERROR_OK); // verify - error_code = cm_verify_step_uarch(&hash1, _access_log, &hash2); + error_code = cm_verify_step_uarch(nullptr, &hash1, _access_log, &hash2); BOOST_CHECK_EQUAL(error_code, CM_ERROR_OK); // step 3 @@ -1275,7 +1250,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(log_step_uarch_until_halt, access_log_machine_fix error_code = cm_get_root_hash(_machine, &hash3); BOOST_REQUIRE_EQUAL(error_code, CM_ERROR_OK); // verify - error_code = cm_verify_step_uarch(&hash2, _access_log, &hash3); + error_code = cm_verify_step_uarch(nullptr, &hash2, _access_log, &hash3); BOOST_CHECK_EQUAL(error_code, CM_ERROR_OK); // step 4 error_code = cm_log_step_uarch(_machine, _log_type, &_access_log); @@ -1284,7 +1259,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(log_step_uarch_until_halt, access_log_machine_fix error_code = cm_get_root_hash(_machine, &hash4); BOOST_REQUIRE_EQUAL(error_code, CM_ERROR_OK); // verify - error_code = cm_verify_step_uarch(&hash3, _access_log, &hash4); + error_code = cm_verify_step_uarch(_machine, &hash3, _access_log, &hash4); BOOST_CHECK_EQUAL(error_code, CM_ERROR_OK); // at micro cycle 4 @@ -1314,7 +1289,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(step_complex_test, access_log_machine_fixture) { BOOST_REQUIRE_EQUAL(error_code, CM_ERROR_OK); BOOST_REQUIRE_EQUAL(std::string(cm_get_last_error_message()), std::string("")); - error_code = cm_verify_step_uarch(&hash0, _access_log, &hash1); + error_code = cm_verify_step_uarch(_machine, &hash0, _access_log, &hash1); BOOST_CHECK_EQUAL(error_code, CM_ERROR_OK); BOOST_CHECK_EQUAL(std::string(""), std::string(cm_get_last_error_message())); } diff --git a/tests/scripts/test-cmio.sh b/tests/scripts/test-cmio.sh index db18cd181..4dc7467d1 100755 --- a/tests/scripts/test-cmio.sh +++ b/tests/scripts/test-cmio.sh @@ -32,8 +32,8 @@ tests=( ) is_server_running () { - echo $cartesi_machine --remote-address=$server_address --max-mcycle=0 - eval $cartesi_machine --remote-address=$server_address --max-mcycle=0 &> /dev/null + echo $cartesi_machine --remote-address=$server_address --remote-health-check + eval $cartesi_machine --remote-address=$server_address --remote-health-check } wait_for_server () { @@ -49,19 +49,13 @@ wait_for_server () { } wait_for_shutdown () { + sleep 1 pid=$1 - - for i in $(seq 1 10); do - if ps -p $pid > /dev/null; then - kill $pid - echo "waiting for pid: $pid..." - sleep 1 - fi - done - - if ps -p $pid > /dev/null; then - kill $pid - echo "$0 killed $pid (server was still running after shutdown)" >&2 + if ps -g $pid > /dev/null; then + kill -- -$pid + echo >&2 + echo "FAILED: servers still running after tests" >&2 + echo >&2 exit 1 fi } @@ -82,4 +76,3 @@ do done eval "$lua $script_dir/../lua/cmio-test.lua local" - diff --git a/tests/scripts/test-jsonrpc-server.sh b/tests/scripts/test-jsonrpc-server.sh index 4588d7c74..13e80ddc1 100755 --- a/tests/scripts/test-jsonrpc-server.sh +++ b/tests/scripts/test-jsonrpc-server.sh @@ -35,8 +35,8 @@ tests=( ) is_server_running () { - echo $cartesi_machine --remote-address=$server_address --max-mcycle=0 - eval $cartesi_machine --remote-address=$server_address --max-mcycle=0 &> /dev/null + echo $cartesi_machine --remote-address=$server_address --remote-health-check + eval $cartesi_machine --remote-address=$server_address --remote-health-check } wait_for_server () { @@ -55,10 +55,12 @@ wait_for_server () { wait_for_shutdown () { pid=$1 sleep 1 - if ps -p $pid > /dev/null + if ps -g $pid > /dev/null then - kill $pid - echo "$0 killed $pid (server was still running after shutdown)" >&2 + kill -- -$pid + echo >&2 + echo "FAILED: servers still running after tests" >&2 + echo >&2 exit 1 fi }