From 913d219d94221f8cc0810720f1eaff20700539f3 Mon Sep 17 00:00:00 2001 From: Savinda Senevirathne Date: Tue, 8 Jun 2021 12:38:15 +0530 Subject: [PATCH] Running hp docker from Sashimono Agent (#5) * Updated ReadMe --- CMakeLists.txt | 6 +- README.md | 23 +- dependencies/.gitignore | 1 + dev-setup.sh | 9 + examples/message-board/message-board.js | 20 +- src/comm/comm_session.cpp | 39 +++- src/conf.cpp | 59 ++++- src/conf.hpp | 22 +- src/crypto.cpp | 63 +++++ src/crypto.hpp | 24 ++ src/hp_manager.cpp | 291 ++++++++++++++++++++++++ src/hp_manager.hpp | 37 +++ src/main.cpp | 9 +- src/msg/json/msg_json.cpp | 106 ++++++--- src/msg/json/msg_json.hpp | 5 +- src/msg/msg_common.hpp | 17 +- src/msg/msg_parser.cpp | 9 +- src/msg/msg_parser.hpp | 4 +- src/pchheader.hpp | 2 + src/sqlite.cpp | 179 ++++++++++++++- src/sqlite.hpp | 11 +- src/util/util.cpp | 57 +++++ src/util/util.hpp | 8 + 23 files changed, 923 insertions(+), 78 deletions(-) create mode 100644 dependencies/.gitignore create mode 100644 src/crypto.cpp create mode 100644 src/crypto.hpp create mode 100644 src/hp_manager.cpp create mode 100644 src/hp_manager.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b33a8d5..752523f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,13 +18,16 @@ add_executable(sagent src/comm/comm_session.cpp src/util/util.cpp src/salog.cpp + src/crypto.cpp src/sqlite.cpp - src/main.cpp + src/hp_manager.cpp src/msg/msg_parser.cpp src/msg/json/msg_json.cpp + src/main.cpp ) target_link_libraries(sagent + libsodium.a libboost_stacktrace_backtrace.a sqlite3 pthread @@ -33,6 +36,7 @@ target_link_libraries(sagent add_custom_command(TARGET sagent POST_BUILD COMMAND cp ./dependencies/bin/hpws ./build/ + COMMAND cp -r ./dependencies/default_contract ./build/ ) target_precompile_headers(sagent PUBLIC src/pchheader.hpp) diff --git a/README.md b/README.md index f8af004..072862a 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,17 @@ A C++ version of sashimono agent ## Libraries +* Crypto - Libsodium https://github.com/jedisct1/libsodium * jsoncons (for JSON and BSON) - https://github.com/danielaparker/jsoncons * Boost Stacktrace - https://www.boost.org * Reader Writer Queue - https://github.com/cameron314/readerwriterqueue * Concurrent Queue - https://github.com/cameron314/concurrentqueue +* Boost Stacktrace - https://www.boost.org ## Setting up Sashimono Agent environment -Run the setup script located at the repo root (tested on Ubuntu 18.04). +- Place a hotpocket contract named **default_contract** (Copies of this contract will be made when new instances are created) inside the **dependencies** directory. This should be a new contract (Created by `hpcore new`) which has configured binaries in hp.cfg. In the future this will be placed by the installation process. + +- Run the setup script located at the repo root (tested on Ubuntu 18.04). ``` ./dev-setup.sh ``` @@ -24,14 +28,19 @@ Run the setup script located at the repo root (tested on Ubuntu 18.04). ## Code structure Code is divided into subsystems via namespaces. -**conf::** Handles configuration. Loads and holds the central configuration object. Used by most of the subsystems. - -**salog::** Handles logging. Creates and prints the logs according to the configured log section in the json config. - **comm::** Handles generic web sockets communication functionality. Mainly acts as a wrapper for [hpws](https://github.com/RichardAH/hpws). -**util::** Contains shared data structures/helper functions used by multiple subsystems. +**conf::** Handles configuration. Loads and holds the central configuration object. Used by most of the subsystems. + +**crypto::** Handles cryptographic activities. Wraps libsodium and offers convenience functions. + +**hp::** Contains hotpocket instance management related helper functions. **msg::** Extract message data from received raw messages. -**sqlite::** Contains sqlite database management related helper functions. \ No newline at end of file +**salog::** Handles logging. Creates and prints the logs according to the configured log section in the json config. + +**sqlite::** Contains sqlite database management related helper functions. + +**util::** Contains shared data structures/helper functions used by multiple subsystems. + diff --git a/dependencies/.gitignore b/dependencies/.gitignore new file mode 100644 index 0000000..b5d15ce --- /dev/null +++ b/dependencies/.gitignore @@ -0,0 +1 @@ +default_contract \ No newline at end of file diff --git a/dev-setup.sh b/dev-setup.sh index 4ece42b..657ec38 100755 --- a/dev-setup.sh +++ b/dev-setup.sh @@ -20,6 +20,15 @@ sudo cp -r $cmake/bin/* /usr/local/bin/ sudo cp -r $cmake/share/* /usr/local/share/ rm $cmake.tar.gz && rm -r $cmake +# Libsodium +wget https://download.libsodium.org/libsodium/releases/libsodium-1.0.18-stable.tar.gz +tar -zxvf libsodium-1.0.18-stable.tar.gz +pushd libsodium-stable > /dev/null 2>&1 +./configure && make +sudo make install +popd > /dev/null 2>&1 +rm libsodium-1.0.18-stable.tar.gz && rm -r libsodium-stable + # jsoncons wget https://github.com/danielaparker/jsoncons/archive/v0.153.3.tar.gz tar -zxvf v0.153.3.tar.gz diff --git a/examples/message-board/message-board.js b/examples/message-board/message-board.js index 6a47200..2836ec8 100644 --- a/examples/message-board/message-board.js +++ b/examples/message-board/message-board.js @@ -22,7 +22,7 @@ const wss = new WebSocket.Server({ server }); wss.on('connection', (ws) => { ws.on('message', (msg) => { - console.log('Received: ', Buffer.from(msg).toString()); + console.log('Received: ', JSON.parse(Buffer.from(msg).toString())); }); }); @@ -46,10 +46,10 @@ const sendToAll = (msg) => { }); } -const askForContractId = () => { +const askForContainerName = () => { return new Promise(resolve => { - rl.question('Contract Id? ', (contractId) => { - resolve(contractId); + rl.question('Container name? ', (containerName) => { + resolve(containerName); }) }) } @@ -75,30 +75,30 @@ server.listen(8080, () => { })); break; case 'destroy': - contractId = await askForContractId(); + containerName = await askForContainerName(); sendToAll(JSON.stringify({ id: uuidv4(), type: 'destroy', owner_pubkey: 'ed7a4b931bdc5dd79b77a8b6ac293d998c123db42bb3ec2613', - contract_id: contractId + container_name: containerName })) break; case 'start': - contractId = await askForContractId(); + containerName = await askForContainerName(); sendToAll(JSON.stringify({ id: uuidv4(), type: 'start', owner_pubkey: 'ed7a4b931bdc5dd79b77a8b6ac293d998c123db42bb3ec2613', - contract_id: contractId + container_name: containerName })) break; case 'stop': - contractId = await askForContractId(); + containerName = await askForContainerName(); sendToAll(JSON.stringify({ id: uuidv4(), type: 'stop', owner_pubkey: 'ed7a4b931bdc5dd79b77a8b6ac293d998c123db42bb3ec2613', - contract_id: contractId + container_name: containerName })) break; diff --git a/src/comm/comm_session.cpp b/src/comm/comm_session.cpp index 9e1dbde..f144f08 100644 --- a/src/comm/comm_session.cpp +++ b/src/comm/comm_session.cpp @@ -1,5 +1,6 @@ #include "comm_session.hpp" #include "../util/util.hpp" +#include "../hp_manager.hpp" namespace comm { @@ -30,7 +31,7 @@ namespace comm // Send an initial message to the host. std::string res; - msg_parser.create_response(res, msg::MSGTYPE_INIT, "Connection initiated."); + msg_parser.build_response(res, msg::MSGTYPE_INIT, {}, "Connection initiated."); send(res); LOG_DEBUG << "Session started: " << uniqueid; } @@ -172,8 +173,13 @@ namespace comm if (msg_parser.extract_create_message(msg) == -1) return -1; id = msg.id; - LOG_INFO << "---------------Create signal received--------------"; - LOG_INFO << "---------------Pubkey: " << msg.pubkey << "--------------"; + hp::instance_info info; + if (hp::create_new_instance(info, msg.pubkey) == -1) + return -1; + + std::string res; + msg_parser.build_create_response(res, info, msg.id); + send(res); } else if (type == msg::MSGTYPE_DESTROY) { @@ -181,8 +187,12 @@ namespace comm if (msg_parser.extract_destroy_message(msg)) return -1; id = msg.id; - LOG_INFO << "---------------Destroy signal received--------------"; - LOG_INFO << "---------------Pubkey: " << msg.pubkey << ", ContractId: " << msg.contract_id << "--------------"; + if (hp::destroy_container(msg.container_name) == -1) + return -1; + + std::string res; + msg_parser.build_response(res, msg::MSGTYPE_DESTROY_RES, msg.id, "Destroyed"); + send(res); } else if (type == msg::MSGTYPE_START) { @@ -190,8 +200,12 @@ namespace comm if (msg_parser.extract_start_message(msg)) return -1; id = msg.id; - LOG_INFO << "---------------Start signal received--------------"; - LOG_INFO << "---------------Pubkey: " << msg.pubkey << ", ContractId: " << msg.contract_id << "--------------"; + if (hp::start_container(msg.container_name) == -1) + return -1; + + std::string res; + msg_parser.build_response(res, msg::MSGTYPE_START_RES, msg.id, "Started"); + send(res); } else if (type == msg::MSGTYPE_STOP) { @@ -199,8 +213,12 @@ namespace comm if (msg_parser.extract_stop_message(msg)) return -1; id = msg.id; - LOG_INFO << "---------------Stop signal received--------------"; - LOG_INFO << "---------------Pubkey: " << msg.pubkey << ", ContractId: " << msg.contract_id << "--------------"; + if (hp::stop_container(msg.container_name) == -1) + return -1; + + std::string res; + msg_parser.build_response(res, msg::MSGTYPE_STOP_RES, msg.id, "Stopped"); + send(res); } else { @@ -208,9 +226,6 @@ namespace comm return -1; } - std::string res; - msg_parser.create_response(res, type, "Acknowledgment for message " + id); - send(res); return 0; } diff --git a/src/conf.cpp b/src/conf.cpp index bba8bdb..9c3ceee 100644 --- a/src/conf.cpp +++ b/src/conf.cpp @@ -46,7 +46,8 @@ namespace conf { // Recursivly create contract directory. Return an error if unable to create if (util::create_dir_tree_recursive(ctx.config_dir) == -1 || - util::create_dir_tree_recursive(ctx.log_dir) == -1) + util::create_dir_tree_recursive(ctx.log_dir) == -1 || + util::create_dir_tree_recursive(ctx.data_dir) == -1) { std::cerr << "ERROR: unable to create directories.\n"; return -1; @@ -59,6 +60,8 @@ namespace conf sa_config cfg = {}; cfg.version = "0.0.1"; + cfg.hp.init_peer_port = 22861; + cfg.hp.init_user_port = 8081; cfg.server.ip_port = {}; cfg.log.max_file_count = 50; cfg.log.max_mbytes_per_file = 10; @@ -95,10 +98,12 @@ namespace conf // Take the parent directory path. ctx.exe_dir = dirname(exepath.data()); - ctx.hpws_exe_path = ctx.exe_dir + "/" + "hpws"; + ctx.hpws_exe_path = ctx.exe_dir + "/hpws"; + ctx.default_contract_path = ctx.exe_dir + "/default_contract"; ctx.config_dir = ctx.exe_dir + "/cfg"; ctx.config_file = ctx.config_dir + "/sa.cfg"; ctx.log_dir = ctx.exe_dir + "/log"; + ctx.data_dir = ctx.exe_dir + "/data"; } /** @@ -107,9 +112,10 @@ namespace conf */ int validate_dir_paths() { - const std::string paths[3] = { + const std::string paths[4] = { ctx.config_file, ctx.log_dir, + ctx.data_dir, ctx.hpws_exe_path}; for (const std::string &path : paths) @@ -178,6 +184,41 @@ namespace conf std::string jpath; + { + jpath = "hp"; + + try + { + const jsoncons::ojson &hp = d["hp"]; + // Check whether the instance_folder is specified. + cfg.hp.instance_folder = hp["instance_folder"].as(); + if (cfg.hp.instance_folder.empty()) + { + std::cerr << "Hp instance folder path is missing.\n"; + return -1; + } + + cfg.hp.init_peer_port = hp["init_peer_port"].as(); + if (cfg.hp.init_peer_port <= 1024) + { + std::cerr << "Configured init peer port invalid. Should be greater than 1024\n"; + return -1; + } + + cfg.hp.init_user_port = hp["init_user_port"].as(); + if (cfg.hp.init_user_port <= 1024) + { + std::cerr << "Configured init user port invalid. Should be greater than 1024\n"; + return -1; + } + } + catch (const std::exception &e) + { + print_missing_field_error(jpath, e); + return -1; + } + } + // server { jpath = "server"; @@ -185,7 +226,7 @@ namespace conf try { const jsoncons::ojson &server = d["server"]; - + cfg.server.ip_port.host_address = server["host"].as(); cfg.server.ip_port.port = server["port"].as(); @@ -245,6 +286,16 @@ namespace conf jsoncons::ojson d; d.insert_or_assign("version", cfg.version); + // Hp configs. + { + jsoncons::ojson hp_config; + hp_config.insert_or_assign("instance_folder", cfg.hp.instance_folder); + hp_config.insert_or_assign("init_peer_port", cfg.hp.init_peer_port); + hp_config.insert_or_assign("init_user_port", cfg.hp.init_user_port); + + d.insert_or_assign("hp", hp_config); + } + // Server configs. { jsoncons::ojson server_config; diff --git a/src/conf.hpp b/src/conf.hpp index ec7143a..ff262c3 100644 --- a/src/conf.hpp +++ b/src/conf.hpp @@ -54,22 +54,32 @@ namespace conf host_ip_port ip_port; }; + struct hp_config + { + std::string instance_folder; + uint16_t init_peer_port; + uint16_t init_user_port; + }; + struct sa_config { std::string version; + hp_config hp; server_config server; log_config log; }; struct sa_context { - std::string command; // The CLI command issued to launch Sashimono agent - std::string exe_dir; // Hot Pocket executable dir. - std::string hpws_exe_path; // hpws executable file path. + std::string command; // The CLI command issued to launch Sashimono agent + std::string exe_dir; // Hot Pocket executable dir. + std::string hpws_exe_path; // hpws executable file path. + std::string default_contract_path; // Path to default contract. - std::string config_dir; // Config dir full path. - std::string config_file; // Full path to the config file. - std::string log_dir; // Log directory full path. + std::string config_dir; // Config dir full path. + std::string config_file; // Full path to the config file. + std::string log_dir; // Log directory full path. + std::string data_dir; // Data directory full path. }; // Global context struct exposed to the application. diff --git a/src/crypto.cpp b/src/crypto.cpp new file mode 100644 index 0000000..f7af984 --- /dev/null +++ b/src/crypto.cpp @@ -0,0 +1,63 @@ +#include "crypto.hpp" +#include "util/util.hpp" + +namespace crypto +{ + + /** + * Initializes the crypto subsystem. Must be called once during application startup. + * @return 0 for successful initialization. -1 for failure. + */ + int init() + { + if (sodium_init() < 0) + { + std::cerr << "sodium_init failed.\n"; + return -1; + } + + return 0; + } + + /** + * Generates a signing key pair using libsodium and assigns them to the provided strings. + */ + void generate_signing_keys(std::string &pubkey, std::string &seckey) + { + // Generate key pair using libsodium default algorithm. + // Currently using ed25519. So append prefix byte to represent that. + + pubkey.resize(crypto_sign_ed25519_PUBLICKEYBYTES + 1); + pubkey[0] = KEYPFX_ed25519; + + seckey.resize(crypto_sign_ed25519_SECRETKEYBYTES + 1); + seckey[0] = KEYPFX_ed25519; + + crypto_sign_ed25519_keypair( + reinterpret_cast(pubkey.data() + 1), // +1 to skip the prefix byte. + reinterpret_cast(seckey.data() + 1)); // +1 to skip the prefix byte. + } + + /** + * Generate random bytes of specified length. + */ + void random_bytes(std::string &result, const size_t len) + { + result.resize(len); + randombytes_buf(result.data(), len); + } + + const std::string generate_uuid() + { + std::string rand_bytes; + random_bytes(rand_bytes, 16); + + // Set bits for UUID v4 variant 1. + uint8_t *uuid = (uint8_t *)rand_bytes.data(); + uuid[6] = (uuid[8] & 0x0F) | 0x40; + uuid[8] = (uuid[8] & 0xBF) | 0x80; + + const std::string hex = util::to_hex(rand_bytes); + return hex.substr(0, 8) + "-" + hex.substr(8, 4) + "-" + hex.substr(12, 4) + "-" + hex.substr(16, 4) + "-" + hex.substr(20); + } +} \ No newline at end of file diff --git a/src/crypto.hpp b/src/crypto.hpp new file mode 100644 index 0000000..e2749e5 --- /dev/null +++ b/src/crypto.hpp @@ -0,0 +1,24 @@ +#ifndef _SA_CRYPTO_ +#define _SA_CRYPTO_ + +#include "pchheader.hpp" + +/** + * Offers convenience functions for cryptographic operations wrapping libsodium. + * These functions are used for config and user/peer message authentication. + */ +namespace crypto +{ + + // Prefix byte to append to ed25519 keys. + static unsigned char KEYPFX_ed25519 = 0xED; + + int init(); + + void random_bytes(std::string &result, const size_t len); + + void generate_signing_keys(std::string &pubkey, std::string &seckey); + + const std::string generate_uuid(); +} +#endif \ No newline at end of file diff --git a/src/hp_manager.cpp b/src/hp_manager.cpp new file mode 100644 index 0000000..175dab9 --- /dev/null +++ b/src/hp_manager.cpp @@ -0,0 +1,291 @@ +#include "hp_manager.hpp" +#include "conf.hpp" +#include "crypto.hpp" +#include "util/util.hpp" +#include "sqlite.hpp" + +namespace hp +{ + // Keep track of the ports of the most recent hp instance. + uint16_t last_user_port, last_peer_port; + + constexpr int FILE_PERMS = 0644; + + sqlite3 *db = NULL; // Database connection for hp related sqlite stuff. + + /** + * Initialize hp related environment. + */ + int init() + { + const std::string db_path = conf::ctx.data_dir + "/hp_instances.sqlite"; + if (sqlite::open_db(db_path, &db, true) == -1 || + sqlite::initialize_hp_db(db) == -1) + { + LOG_ERROR << "Error preparing hp database in " << db_path; + return -1; + } + const int peer_port_db = sqlite::get_max_port(db, "peer_port"); + last_peer_port = peer_port_db == 0 ? (conf::cfg.hp.init_peer_port -1) : peer_port_db; + + const int user_port_db = sqlite::get_max_port(db, "user_port"); + last_user_port = user_port_db == 0 ? (conf::cfg.hp.init_user_port -1) : user_port_db; + + return 0; + } + + /** + * Do hp related cleanups. + */ + void deinit() + { + if (db != NULL) + sqlite::close_db(&db); + } + + /** + * Create a new instance of hotpocket. A new contract is created and then the docker images is run on that. + * @param info Structure holding the generated instance info. + * @param owner_pubkey Public key of the instance owner. + * @return 0 on success and -1 on error. + */ + int create_new_instance(instance_info &info, std::string_view owner_pubkey) + { + const uint16_t user_port = last_user_port + 1; + const uint16_t peer_port = last_peer_port + 1; + + const std::string name = crypto::generate_uuid(); // This will be the docker container name as well as the contract folder name. + + if (create_contract(info, name, peer_port, user_port) != 0 || + run_container(name, user_port, peer_port) != 0 || // Gives 3200 if docker failed. + sqlite::insert_hp_instance_row(db, owner_pubkey, info, CONTAINER_STATES[STATES::RUNNING]) == -1) + { + LOG_ERROR << errno << ": Error creating and running new hp instance for " << owner_pubkey; + return -1; + } + + last_user_port++; + last_peer_port++; + + return 0; + } + + /** + * Runs a hotpocket docker image on the given contract and the ports. + * @param folder_name Contract directory folder name. + * @param user_port User port to be assigned. + * @param peer_port Peer port to be assigned. + * @return 0 on success execution or relavent error code on error. + */ + int run_container(const std::string &folder_name, const uint16_t user_port, const uint16_t peer_port) + { + const std::string command = "docker run -t -i -d --network=hpnet --stop-signal=SIGINT --name=" + folder_name + " \ + -p " + + std::to_string(user_port) + ":" + std::to_string(user_port) + " \ + -p " + + std::to_string(peer_port) + ":" + std::to_string(peer_port) + " \ + --device /dev/fuse --cap-add SYS_ADMIN --security-opt apparmor:unconfined \ + --mount type=bind,source=" + + conf::cfg.hp.instance_folder + "/" + + folder_name + ",target=/contract \ + hpcore:latest run /contract"; + + return system(command.c_str()); + } + + /** + * Stops the container with given name if exists. + * @param container_name Name of the container. + * @return 0 on success execution or relavent error code on error. + */ + int stop_container(const std::string &container_name) + { + const int res = sqlite::is_container_exists_in_status(db, container_name, CONTAINER_STATES[STATES::RUNNING]); + if (res == 0) + { + LOG_ERROR << "Given container not found. name: " << container_name; + return -1; + } + else if (res == 1) + { + LOG_ERROR << "Given container is not running. name: " << container_name; + return -1; + } + const std::string command = "docker stop " + container_name; + if (system(command.c_str()) != 0 || sqlite::update_status_in_container(db, container_name, CONTAINER_STATES[STATES::STOPPED]) == -1) + { + LOG_ERROR << "Error when stopping container. name: " << container_name; + return -1; + } + + return 0; + } + + /** + * Starts the container with given name if exists. + * @param container_name Name of the container. + * @return 0 on success execution or relavent error code on error. + */ + int start_container(const std::string &container_name) + { + const int res = sqlite::is_container_exists_in_status(db, container_name, CONTAINER_STATES[STATES::STOPPED]); + if (res == 0) + { + LOG_ERROR << "Given container not found. name: " << container_name; + return -1; + } + else if (res == 1) + { + LOG_ERROR << "Given container is not stopped. name: " << container_name; + return -1; + } + const std::string command = "docker start " + container_name; + if (system(command.c_str()) != 0 || sqlite::update_status_in_container(db, container_name, CONTAINER_STATES[STATES::RUNNING]) == -1) + { + LOG_ERROR << "Error when starting container. name: " << container_name; + return -1; + } + + return 0; + } + + /** + * Destroy the container with given name if exists. + * @param container_name Name of the container. + * @return 0 on success execution or relavent error code on error. + */ + int destroy_container(const std::string &container_name) + { + const int res = sqlite::is_container_exists_in_status(db, container_name, CONTAINER_STATES[STATES::STOPPED]); + if (res == 0) + { + LOG_ERROR << "Given container not found. name: " << container_name; + return -1; + } + const std::string command = "docker container rm -f " + container_name; + const std::string folder_path = conf::cfg.hp.instance_folder + "/" + container_name; + + if (system(command.c_str()) != 0 || sqlite::update_status_in_container(db, container_name, CONTAINER_STATES[STATES::DESTROYED]) == -1 || util::remove_directory_recursively(folder_path) == -1) + { + LOG_ERROR << errno << ": Error destroying container " << container_name; + return -1; + } + return 0; + } + + /** + * Creates a copy of default contract with the given name and the ports in the instance folder given in the config file. + * @param info Information of the created contract instance. + * @param folder_name Folder name for the contract directory. + * @param peer_port Peer port assigned to the hotpocket instance. + * @param user_port User port assigned to the hotpocket instance. + * @return -1 on error and 0 on success. + * + */ + int create_contract(instance_info &info, const std::string &folder_name, const uint16_t peer_port, const uint16_t user_port) + { + const std::string folder_path = conf::cfg.hp.instance_folder + "/" + folder_name; + const std::string command = "cp -r " + conf::ctx.default_contract_path + " " + folder_path; + if (system(command.c_str()) != 0) + { + LOG_ERROR << "Default contract copying failed to " << folder_path; + return -1; + } + + // Read the config file into json document object. + const std::string config_file_path = folder_path + "/cfg/hp.cfg"; + const int config_fd = open(config_file_path.data(), O_RDWR, FILE_PERMS); + if (config_fd == -1) + { + LOG_ERROR << errno << ": Error opening hp config file " << config_file_path; + return -1; + } + + std::string buf; + if (util::read_from_fd(config_fd, buf) == -1) + { + std::cerr << "Error reading from the config file. " << errno << '\n'; + close(config_fd); + return -1; + } + + jsoncons::ojson d; + try + { + d = jsoncons::ojson::parse(buf, jsoncons::strict_json_parsing()); + } + catch (const std::exception &e) + { + std::cerr << "Invalid config file format. " << e.what() << '\n'; + close(config_fd); + return -1; + } + buf.clear(); + + std::string pubkey, seckey; + crypto::generate_signing_keys(pubkey, seckey); + + const std::string contract_id = crypto::generate_uuid(); + const std::string pubkey_hex = util::to_hex(pubkey); + + d["node"]["public_key"] = pubkey_hex; + d["node"]["private_key"] = util::to_hex(seckey); + d["contract"]["id"] = contract_id; + jsoncons::ojson unl(jsoncons::json_array_arg); + unl.push_back(util::to_hex(pubkey)); + d["contract"]["unl"] = unl; + d["mesh"]["port"] = peer_port; + d["user"]["port"] = user_port; + + if (write_json_file(config_fd, d) == -1) + { + LOG_ERROR << "Writing modified hp config failed."; + close(config_fd); + return -1; + } + close(config_fd); + + info.ip = "localhost"; + info.contract_id = contract_id; + info.name = folder_name; + info.pubkey = pubkey_hex; + info.user_port = user_port; + info.peer_port = peer_port; + return 0; + } + + /** + * Writes the given json doc to a file. + * @param fd File descriptor to the open file. + * @param d A valid JSON document. + * @return 0 on success. -1 on failure. + */ + int write_json_file(const int fd, const jsoncons::ojson &d) + { + std::string json; + // Convert json object to a string. + try + { + jsoncons::json_options options; + options.object_array_line_splits(jsoncons::line_split_kind::multi_line); + options.spaces_around_comma(jsoncons::spaces_option::no_spaces); + std::ostringstream os; + os << jsoncons::pretty_print(d, options); + json = os.str(); + os.clear(); + } + catch (const std::exception &e) + { + LOG_ERROR << "Converting modified hp config json to string failed. "; + return -1; + } + + if (ftruncate(fd, 0) == -1 || write(fd, json.data(), json.size()) == -1) + { + LOG_ERROR << "Writing modified hp config file failed. "; + return -1; + } + return 0; + } + +} // namespace hp diff --git a/src/hp_manager.hpp b/src/hp_manager.hpp new file mode 100644 index 0000000..1bc2a7d --- /dev/null +++ b/src/hp_manager.hpp @@ -0,0 +1,37 @@ +#ifndef _SA_HP_MANAGER_ +#define _SA_HP_MANAGER_ + +#include "pchheader.hpp" + +namespace hp +{ + constexpr const char *CONTAINER_STATES[]{"RUNNING", "STOPPED", "DESTROYED"}; + enum STATES + { + RUNNING, + STOPPED, + DESTROYED + }; + + struct instance_info + { + std::string name; + std::string ip; + std::string pubkey; + std::string contract_id; + std::uint16_t peer_port; + std::uint16_t user_port; + }; + + int init(); + void deinit(); + int create_new_instance(instance_info &info, std::string_view owner_pubkey); + int run_container(const std::string &folder_name, const uint16_t user_port, const uint16_t peer_port); + int start_container(const std::string &container_name); + int stop_container(const std::string &container_name); + int destroy_container(const std::string &container_name); + void kill_all_containers(); + int create_contract(instance_info &info, const std::string &folder_name, const uint16_t peer_port, const uint16_t user_port); + int write_json_file(const int fd, const jsoncons::ojson &d); +} // namespace hp +#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index a9a549d..3d03764 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,9 @@ #include "sqlite.hpp" #include "salog.hpp" #include "comm/comm_handler.hpp" +#include "hp_manager.hpp" +#include "crypto.hpp" +#include "hp_manager.hpp" /** * Parses CLI args and extracts sashimono agent command and parameters given. @@ -43,6 +46,7 @@ int parse_cmd(int argc, char **argv) void deinit() { comm::deinit(); + hp::deinit(); } void sig_exit_handler(int signum) @@ -127,11 +131,14 @@ int main(int argc, char **argv) salog::init(); // Initialize logger for SA. + if (crypto::init() == -1) + return -1; + if (conf::ctx.command == "run") { LOG_INFO << "Sashimono agent started. Version : " << conf::cfg.version << " Log level : " << conf::cfg.log.log_level; - if (comm::init() == -1) + if (comm::init() == -1 || hp::init() == -1) { deinit(); return -1; diff --git a/src/msg/json/msg_json.cpp b/src/msg/json/msg_json.cpp index d832c41..0e11c9b 100644 --- a/src/msg/json/msg_json.cpp +++ b/src/msg/json/msg_json.cpp @@ -127,7 +127,7 @@ namespace msg::json * { * "type": "destroy", * "owner_pubkey": "", - * "contract_id": "", + * "container_name": "", * } * @return 0 on successful extraction. -1 for failure. */ @@ -136,21 +136,19 @@ namespace msg::json if (extract_commons(msg.type, msg.id, msg.pubkey, d) == -1) return -1; - if (!d.contains(msg::FLD_CONTRACT_ID)) + if (!d.contains(msg::FLD_CONTAINER_NAME)) { - LOG_ERROR << "Field contract_id is missing."; + LOG_ERROR << "Field container_name is missing."; return -1; } - if (!d[msg::FLD_CONTRACT_ID].is()) + if (!d[msg::FLD_CONTAINER_NAME].is()) { - LOG_ERROR << "Invalid contract_id value."; + LOG_ERROR << "Invalid container_name value."; return -1; } - msg.contract_id = d[msg::FLD_CONTRACT_ID].as(); - return 0; - + msg.container_name = d[msg::FLD_CONTAINER_NAME].as(); return 0; } @@ -162,7 +160,7 @@ namespace msg::json * { * "type": "start", * "owner_pubkey": "", - * "contract_id": "", + * "container_name": "", * } * @return 0 on successful extraction. -1 for failure. */ @@ -171,21 +169,19 @@ namespace msg::json if (extract_commons(msg.type, msg.id, msg.pubkey, d) == -1) return -1; - if (!d.contains(msg::FLD_CONTRACT_ID)) + if (!d.contains(msg::FLD_CONTAINER_NAME)) { - LOG_ERROR << "Field contract_id is missing."; + LOG_ERROR << "Field container_name is missing."; return -1; } - if (!d[msg::FLD_CONTRACT_ID].is()) + if (!d[msg::FLD_CONTAINER_NAME].is()) { - LOG_ERROR << "Invalid contract_id value."; + LOG_ERROR << "Invalid container_name value."; return -1; } - msg.contract_id = d[msg::FLD_CONTRACT_ID].as(); - return 0; - + msg.container_name = d[msg::FLD_CONTAINER_NAME].as(); return 0; } @@ -197,7 +193,7 @@ namespace msg::json * { * "type": "stop", * "owner_pubkey": "", - * "contract_id": "", + * "container_name": "", * } * @return 0 on successful extraction. -1 for failure. */ @@ -206,39 +202,42 @@ namespace msg::json if (extract_commons(msg.type, msg.id, msg.pubkey, d) == -1) return -1; - if (!d.contains(msg::FLD_CONTRACT_ID)) + if (!d.contains(msg::FLD_CONTAINER_NAME)) { - LOG_ERROR << "Field contract_id is missing."; + LOG_ERROR << "Field container_name is missing."; return -1; } - if (!d[msg::FLD_CONTRACT_ID].is()) + if (!d[msg::FLD_CONTAINER_NAME].is()) { - LOG_ERROR << "Invalid contract_id value."; + LOG_ERROR << "Invalid container_name value."; return -1; } - msg.contract_id = d[msg::FLD_CONTRACT_ID].as(); - return 0; - + msg.container_name = d[msg::FLD_CONTAINER_NAME].as(); return 0; } /** - * Constructs a response json. + * Constructs a generic json response. * @param msg Buffer to construct the generated json message string into. * Message format: * { + * 'reply_for': '' * 'type': '', * "content": "" * } * @param response_type Type of the response. * @param content Content inside the response. */ - void create_response(std::string &msg, std::string_view response_type, std::string_view content) + void build_response(std::string &msg, std::string_view response_type, std::string_view reply_for, std::string_view content) { msg.reserve(1024); msg += "{\""; + msg += msg::FLD_REPLY_FOR; + msg += SEP_COLON; + msg += std::string(reply_for); + msg += SEP_COMMA; msg += msg::FLD_TYPE; msg += SEP_COLON; msg += response_type; @@ -249,4 +248,59 @@ namespace msg::json msg += "\"}"; } + /** + * Constructs a json response for create message. + * @param msg Buffer to construct the generated json message string into. + * Message format: + * { + * 'reply_for': '' + * 'type': '', + * "name": "" + * "ip": "" + * "pubkey": "" + * "contract_id": "" + * "peer_port": "" + * "user_port": "" + * } + * @param response_type Type of the response. + * @param content Content inside the response. + */ + void build_create_response(std::string &msg, const hp::instance_info &info, std::string_view reply_for) + { + msg.reserve(1024); + msg += "{\""; + msg += msg::FLD_REPLY_FOR; + msg += SEP_COLON; + msg += std::string(reply_for); + msg += SEP_COMMA; + msg += msg::FLD_TYPE; + msg += SEP_COLON; + msg += msg::MSGTYPE_CREATE_RES; + msg += SEP_COMMA; + msg += "name"; + msg += SEP_COLON; + msg += info.name; + msg += SEP_COMMA; + msg += "ip"; + msg += SEP_COLON; + msg += info.ip; + msg += SEP_COMMA; + msg += "pubkey"; + msg += SEP_COLON; + msg += info.pubkey; + msg += SEP_COMMA; + msg += "contract_id"; + msg += SEP_COLON; + msg += info.contract_id; + msg += SEP_COMMA; + msg += "peer_port"; + msg += SEP_COLON; + msg += std::to_string(info.peer_port); + msg += SEP_COMMA; + msg += "user_port"; + msg += SEP_COLON; + msg += std::to_string(info.user_port); + msg += "\"}"; + } + } // namespace msg::json \ No newline at end of file diff --git a/src/msg/json/msg_json.hpp b/src/msg/json/msg_json.hpp index 449f4a1..09cf634 100644 --- a/src/msg/json/msg_json.hpp +++ b/src/msg/json/msg_json.hpp @@ -3,6 +3,7 @@ #include "../../pchheader.hpp" #include "../msg_common.hpp" +#include "../../hp_manager.hpp" /** * Parser helpers for json messages. @@ -23,7 +24,9 @@ namespace msg::json int extract_stop_message(stop_msg &msg, const jsoncons::json &d); - void create_response(std::string &msg, std::string_view response_type, std::string_view content); + void build_response(std::string &msg, std::string_view response_type, std::string_view reply_for, std::string_view content); + + void build_create_response(std::string &msg, const hp::instance_info &info, std::string_view reply_for); } // namespace msg::json diff --git a/src/msg/msg_common.hpp b/src/msg/msg_common.hpp index 3e21569..15e4264 100644 --- a/src/msg/msg_common.hpp +++ b/src/msg/msg_common.hpp @@ -11,13 +11,13 @@ namespace msg std::string type; std::string pubkey; }; - + struct destroy_msg { std::string id; std::string type; std::string pubkey; - std::string contract_id; + std::string container_name; }; struct start_msg @@ -25,7 +25,7 @@ namespace msg std::string id; std::string type; std::string pubkey; - std::string contract_id; + std::string container_name; }; struct stop_msg @@ -33,14 +33,15 @@ namespace msg std::string id; std::string type; std::string pubkey; - std::string contract_id; + std::string container_name; }; // Message field names constexpr const char *FLD_TYPE = "type"; + constexpr const char *FLD_REPLY_FOR = "reply_for"; constexpr const char *FLD_CONTENT = "content"; constexpr const char *FLD_PUBKEY = "owner_pubkey"; - constexpr const char *FLD_CONTRACT_ID = "contract_id"; + constexpr const char *FLD_CONTAINER_NAME = "container_name"; constexpr const char *FLD_ID = "id"; // Message types @@ -50,6 +51,12 @@ namespace msg constexpr const char *MSGTYPE_START = "start"; constexpr const char *MSGTYPE_STOP = "stop"; + // Message res types + constexpr const char *MSGTYPE_CREATE_RES = "create_res"; + constexpr const char *MSGTYPE_DESTROY_RES = "destroy_res"; + constexpr const char *MSGTYPE_START_RES = "start_res"; + constexpr const char *MSGTYPE_STOP_RES = "stop_res"; + } // namespace msg #endif \ No newline at end of file diff --git a/src/msg/msg_parser.cpp b/src/msg/msg_parser.cpp index da75cc4..dc28125 100644 --- a/src/msg/msg_parser.cpp +++ b/src/msg/msg_parser.cpp @@ -33,9 +33,14 @@ namespace msg return json::extract_stop_message(msg, jdoc); } - void msg_parser::create_response(std::string &msg, std::string_view response_type, std::string_view content) const + void msg_parser::build_response(std::string &msg, std::string_view response_type, std::string_view reply_for, std::string_view content) const { - json::create_response(msg, response_type, content); + json::build_response(msg, response_type, reply_for, content); + } + + void msg_parser::build_create_response(std::string &msg, const hp::instance_info &info, std::string_view reply_for) const + { + json::build_create_response(msg, info, reply_for); } } // namespace msg \ No newline at end of file diff --git a/src/msg/msg_parser.hpp b/src/msg/msg_parser.hpp index d9fb710..2617f0e 100644 --- a/src/msg/msg_parser.hpp +++ b/src/msg/msg_parser.hpp @@ -3,6 +3,7 @@ #include "../pchheader.hpp" #include "msg_common.hpp" +#include "../hp_manager.hpp" namespace msg { @@ -17,7 +18,8 @@ namespace msg int extract_destroy_message(destroy_msg &msg) const; int extract_start_message(start_msg &msg) const; int extract_stop_message(stop_msg &msg) const; - void create_response(std::string &msg, std::string_view response_type, std::string_view content) const; + void build_response(std::string &msg, std::string_view response_type, std::string_view reply_for, std::string_view content) const; + void build_create_response(std::string &msg, const hp::instance_info &info, std::string_view reply_for) const; }; } // namespace msg diff --git a/src/pchheader.hpp b/src/pchheader.hpp index ac1c9cc..99e3173 100644 --- a/src/pchheader.hpp +++ b/src/pchheader.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -15,6 +16,7 @@ #include #include #include +#include #include #include #include diff --git a/src/sqlite.cpp b/src/sqlite.cpp index 2122dfd..7ba95ab 100644 --- a/src/sqlite.cpp +++ b/src/sqlite.cpp @@ -1,5 +1,6 @@ #include "sqlite.hpp" #include "salog.hpp" +#include "util/util.hpp" namespace sqlite { @@ -7,6 +8,7 @@ namespace sqlite constexpr const char *CREATE_TABLE = "CREATE TABLE IF NOT EXISTS "; constexpr const char *CREATE_INDEX = "CREATE INDEX "; constexpr const char *CREATE_UNIQUE_INDEX = "CREATE UNIQUE INDEX "; + constexpr const char *JOURNAL_MODE_OFF = "PRAGMA journal_mode=OFF"; constexpr const char *BEGIN_TRANSACTION = "BEGIN TRANSACTION;"; constexpr const char *COMMIT_TRANSACTION = "COMMIT;"; constexpr const char *ROLLBACK_TRANSACTION = "ROLLBACK;"; @@ -19,14 +21,22 @@ namespace sqlite constexpr const char *WHERE = " WHERE "; constexpr const char *AND = " AND "; + constexpr const char *INSTANCE_TABLE = "instances"; + + constexpr const char *INSERT_INTO_HP_INSTANCE = "INSERT INTO instances(" + "owner_pubkey, time, status, name, ip," + "peer_port, user_port, pubkey, contract_id" + ") VALUES(?,?,?,?,?,?,?,?,?)"; + /** * Opens a connection to a given databse and give the db pointer. * @param db_name Database name to be connected. * @param db Pointer to the db pointer which is to be connected and pointed. * @param writable Whether the database must be opened in a writable mode or not. + * @param journal Whether to enable db journaling or not. * @returns returns 0 on success, or -1 on error. */ - int open_db(std::string_view db_name, sqlite3 **db, const bool writable) + int open_db(std::string_view db_name, sqlite3 **db, const bool writable, const bool journal) { int ret; const int flags = writable ? (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE) : SQLITE_OPEN_READONLY; @@ -37,6 +47,12 @@ namespace sqlite return -1; } + // We can turn off journaling for the db if we don't need transacion support. + // Journaling mode can introduce lot of extra underyling file system operations which may cause + // lot of overhead if used on a low-performance filesystem like hpfs. + if (writable && !journal && exec_sql(*db, JOURNAL_MODE_OFF) == -1) + return -1; + return 0; } @@ -254,4 +270,165 @@ namespace sqlite *db = NULL; return 0; } + + /** + * Initialize hp_instances table. Table is only created if not existed. Indexes are added for name and owner_pubkey fields. + * @param db Database connection. + * @return -1 on error and 0 on success. + */ + int initialize_hp_db(sqlite3 *db) + { + if (!is_table_exists(db, INSTANCE_TABLE)) + { + const std::vector columns{ + table_column_info("owner_pubkey", COLUMN_DATA_TYPE::TEXT), + table_column_info("time", COLUMN_DATA_TYPE::INT), + table_column_info("status", COLUMN_DATA_TYPE::TEXT), + table_column_info("name", COLUMN_DATA_TYPE::TEXT, true), + table_column_info("ip", COLUMN_DATA_TYPE::TEXT), + table_column_info("peer_port", COLUMN_DATA_TYPE::INT), + table_column_info("user_port", COLUMN_DATA_TYPE::INT), + table_column_info("pubkey", COLUMN_DATA_TYPE::TEXT), + table_column_info("contract_id", COLUMN_DATA_TYPE::TEXT)}; + + if (create_table(db, INSTANCE_TABLE, columns) == -1 || + create_index(db, INSTANCE_TABLE, "name", true) == -1 || + create_index(db, INSTANCE_TABLE, "owner_pubkey", false) == -1) // one user can have multiple instances running. + return -1; + } + return 0; + } + + /** + * Inserts a hp instance record. + * @param db Pointer to the db. + * @param info HP instance information. + * @param status Current status of the instance. + * @returns returns 0 on success, or -1 on error. + */ + int insert_hp_instance_row(sqlite3 *db, std::string_view owner_pubkey, const hp::instance_info &info, std::string_view status) + { + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(db, INSERT_INTO_HP_INSTANCE, -1, &stmt, 0) == SQLITE_OK && stmt != NULL && + sqlite3_bind_text(stmt, 1, owner_pubkey.data(), owner_pubkey.length(), SQLITE_STATIC) == SQLITE_OK && + sqlite3_bind_int64(stmt, 2, util::get_epoch_milliseconds()) == SQLITE_OK && + sqlite3_bind_text(stmt, 3, status.data(), status.length(), SQLITE_STATIC) == SQLITE_OK && + sqlite3_bind_text(stmt, 4, info.name.data(), info.name.length(), SQLITE_STATIC) == SQLITE_OK && + sqlite3_bind_text(stmt, 5, info.ip.data(), info.ip.length(), SQLITE_STATIC) == SQLITE_OK && + sqlite3_bind_int64(stmt, 6, info.peer_port) == SQLITE_OK && + sqlite3_bind_int64(stmt, 7, info.user_port) == SQLITE_OK && + sqlite3_bind_text(stmt, 8, info.pubkey.data(), info.pubkey.length(), SQLITE_STATIC) == SQLITE_OK && + sqlite3_bind_text(stmt, 9, info.contract_id.data(), info.contract_id.length(), SQLITE_STATIC) == SQLITE_OK && + sqlite3_step(stmt) == SQLITE_DONE) + { + sqlite3_finalize(stmt); + return 0; + } + + LOG_ERROR << "Error inserting hp instance record. " << sqlite3_errmsg(db); + return -1; + } + + /** + * Checks whether the container exist in the database and checks against the given status. + * @param db Pointer to the db. + * @param container_name Name of the container to be checked. + * @param status Status to check the container status against. + * @returns 0 if not found, 1 if container exists but not in given status and 2 if container exist in given status. + */ + int is_container_exists_in_status(sqlite3 *db, std::string_view container_name, std::string_view status) + { + std::string sql; + // Reserving the space for the query before construction. + sql.reserve(sizeof(SELECT_ALL) + sizeof(SQLITE_MASTER) + sizeof(WHERE) + sizeof(AND) + container_name.size() + 7); + + sql.append(SELECT_ALL); + sql.append(INSTANCE_TABLE); + sql.append(WHERE); + sql.append("name='"); + sql.append(container_name); + sql.append("'"); + + sqlite3_stmt *stmt; + int result = 0; // Not exist. + + if (sqlite3_prepare_v2(db, sql.data(), -1, &stmt, 0) == SQLITE_OK && + stmt != NULL && sqlite3_step(stmt) == SQLITE_ROW) + { + const std::string current_status(reinterpret_cast(sqlite3_column_text(stmt, 2))); + // Finalize and distroys the statement. + sqlite3_finalize(stmt); + if (current_status == status) + result = 2; + else + result = 1; + + return result; + } + + // Finalize and distroys the statement. + sqlite3_finalize(stmt); + return result; + } + + /** + * Update the status of the given container to the new value. + * @param db Database connection. + * @param container_name Name of the container whose status should be updated. + * @param status The new status of the container. + * @return 0 on success and -1 on error. + */ + int update_status_in_container(sqlite3 *db, std::string_view container_name, std::string_view status) + { + std::string sql; + // Reserving the space for the query before construction. + sql.reserve(sizeof(INSTANCE_TABLE) + status.length() + sizeof(WHERE) + container_name.size() + 30); + sql.append("UPDATE "); + sql.append(INSTANCE_TABLE); + sql.append(" SET status = '"); + sql.append(status); + sql.append("'"); + sql.append(WHERE); + sql.append("name='"); + sql.append(container_name); + sql.append("'"); + + return sqlite::exec_sql(db, sql); + } + + /** + * Get the max port already used for the instances. Ports used for already destroyed instances are excluded. + * @param db Database connection. + * @param column_name Name of the column. Should be one of ['peer_port', 'user_port']. + * @return The port number. 0 is returned if no data found on database or on database error. + */ + int get_max_port(sqlite3 *db, std::string_view column_name) + { + std::string sql; + // Reserving the space for the query before construction. + sql.reserve(sizeof(INSTANCE_TABLE) + column_name.length() + sizeof(WHERE) + sizeof(hp::CONTAINER_STATES[hp::STATES::DESTROYED]) + 29); + sql.append("SELECT max(") + .append(column_name) + .append(") from ") + .append(INSTANCE_TABLE) + .append(WHERE) + .append("status !='") + .append(hp::CONTAINER_STATES[hp::STATES::DESTROYED]) + .append("'"); + + sqlite3_stmt *stmt; + + if (sqlite3_prepare_v2(db, sql.data(), -1, &stmt, 0) == SQLITE_OK && + stmt != NULL && sqlite3_step(stmt) == SQLITE_ROW) + { + const int result = sqlite3_column_int64(stmt, 0); + // Finalize and distroys the statement. + sqlite3_finalize(stmt); + return result; + } + + // Finalize and distroys the statement. + sqlite3_finalize(stmt); + return 0; + } } diff --git a/src/sqlite.hpp b/src/sqlite.hpp index 872afd7..1ad5840 100644 --- a/src/sqlite.hpp +++ b/src/sqlite.hpp @@ -2,6 +2,7 @@ #define _SA_SQLITE_ #include "pchheader.hpp" +#include "hp_manager.hpp" namespace sqlite { @@ -38,7 +39,7 @@ namespace sqlite } }; - int open_db(std::string_view db_name, sqlite3 **db, const bool writable = false); + int open_db(std::string_view db_name, sqlite3 **db, const bool writable = false, const bool journal = true); int exec_sql(sqlite3 *db, std::string_view sql, int (*callback)(void *, int, char **, char **) = NULL, void *callback_first_arg = NULL); @@ -60,6 +61,14 @@ namespace sqlite int close_db(sqlite3 **db); + int initialize_hp_db(sqlite3 *db); + int insert_hp_instance_row(sqlite3 *db, std::string_view owner_pubkey, const hp::instance_info &info, std::string_view status); + + int is_container_exists_in_status(sqlite3 *db, std::string_view name, std::string_view status); + + int update_status_in_container(sqlite3 *db, std::string_view container_name, std::string_view status); + + int get_max_port(sqlite3 *db, std::string_view column_name); } #endif diff --git a/src/util/util.cpp b/src/util/util.cpp index f89a4cb..b97c8f2 100644 --- a/src/util/util.cpp +++ b/src/util/util.cpp @@ -3,6 +3,39 @@ namespace util { + const std::string to_hex(const std::string_view bin) + { + // Allocate the target string. + std::string encoded_string; + encoded_string.resize(bin.size() * 2); + + // Get encoded string. + sodium_bin2hex( + encoded_string.data(), + encoded_string.length() + 1, // + 1 because sodium writes ending '\0' character as well. + reinterpret_cast(bin.data()), + bin.size()); + return encoded_string; + } + + const std::string to_bin(const std::string_view hex) + { + std::string bin; + bin.resize(hex.size() / 2); + + const char *hex_end; + size_t bin_len; + if (sodium_hex2bin( + reinterpret_cast(bin.data()), bin.size(), + hex.data(), hex.size(), + "", &bin_len, &hex_end)) + { + return ""; // Empty indicates error. + } + + return bin; + } + /** * Check whether given directory exists. * @param path Directory path. @@ -136,4 +169,28 @@ namespace util std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); } + /** + * Returns current time in UNIX epoch milliseconds. + */ + uint64_t get_epoch_milliseconds() + { + return std::chrono::duration_cast>( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + } + + /** + * Remove a directory recursively with it's content. FTW_DEPTH is provided so all of the files and subdirectories within + * The path will be processed. FTW_PHYS is provided so symbolic links won't be followed. + */ + int remove_directory_recursively(std::string_view dir_path) + { + return nftw( + dir_path.data(), [](const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) + { + return remove(fpath); + }, + 1, FTW_DEPTH | FTW_PHYS); + } + } // namespace util diff --git a/src/util/util.hpp b/src/util/util.hpp index 8aba668..4855cef 100644 --- a/src/util/util.hpp +++ b/src/util/util.hpp @@ -8,6 +8,10 @@ */ namespace util { + const std::string to_hex(const std::string_view bin); + + const std::string to_bin(const std::string_view hex); + bool is_dir_exists(std::string_view path); bool is_file_exists(std::string_view path); @@ -24,6 +28,10 @@ namespace util void sleep(const uint64_t milliseconds); + uint64_t get_epoch_milliseconds(); + + int remove_directory_recursively(std::string_view dir_path); + } // namespace util #endif