#include "msg_json.hpp" #include "../../util/util.hpp" namespace msg::json { // JSON separators constexpr const char *SEP_COMMA = "\",\""; constexpr const char *SEP_COLON = "\":\""; constexpr const char *SEP_COMMA_NOQUOTE = ",\""; constexpr const char *SEP_COLON_NOQUOTE = "\":"; constexpr const char *DOUBLE_QUOTE = "\""; constexpr uint16_t MOMENT_SIZE = 900; // XRP ledgers per Moment. constexpr uint16_t LEDGER_TIME_APPROX = 4000; // Approx. milliseconds per XRP ledger. /** * Parses a json message sent by the message board. * @param d Jsoncons document to which the parsed json should be loaded. * @param message The message to parse. * Accepted message format: * { * 'type': '' * ... * } * @return 0 on successful parsing. -1 for failure. */ int parse_message(jsoncons::json &d, std::string_view message) { try { d = jsoncons::json::parse(message, jsoncons::strict_json_parsing()); } catch (const std::exception &e) { LOG_ERROR << "JSON message parsing failed. " << e.what(); return -1; } // Check existence of msg type field. if (!d.contains(msg::FLD_TYPE) || !d[msg::FLD_TYPE].is()) { LOG_ERROR << "JSON message 'type' missing or invalid."; return -1; } return 0; } /** * Extracts the message 'type' values from the json document. */ int extract_type(std::string &extracted_type, const jsoncons::json &d) { if (!d.contains(msg::FLD_TYPE)) { LOG_ERROR << "Field type is missing."; return -1; } if (!d[msg::FLD_TYPE].is()) { LOG_ERROR << "Invalid type value."; return -1; } extracted_type = d[msg::FLD_TYPE].as(); return 0; } /** * Extracts create message from msg. * @param msg Populated msg object. * @param d The json document holding the read request message. * Accepted signed input container format: * { * "type": "create", * "owner_pubkey": "" * "contract_id": "", * "image": "" * } * @return 0 on successful extraction. -1 for failure. */ int extract_create_message(create_msg &msg, const jsoncons::json &d) { if (extract_type(msg.type, d) == -1) return -1; if (!d.contains(msg::FLD_CONTAINER_NAME)) { LOG_ERROR << "Field container_name is missing."; return -1; } if (!d[msg::FLD_CONTAINER_NAME].is()) { LOG_ERROR << "Invalid container_name value."; return -1; } if (!d.contains(msg::FLD_PUBKEY)) { LOG_ERROR << "Field owner_pubkey is missing."; return -1; } if (!d.contains(msg::FLD_CONTRACT_ID)) { LOG_ERROR << "Field contract_id is missing."; return -1; } if (!d.contains(msg::FLD_IMAGE)) { LOG_ERROR << "Field image is missing."; return -1; } if (!d[msg::FLD_PUBKEY].is()) { LOG_ERROR << "Invalid owner_pubkey value."; return -1; } if (!d[msg::FLD_CONTRACT_ID].is()) { LOG_ERROR << "Invalid contract_id value."; return -1; } if (!d[msg::FLD_IMAGE].is()) { LOG_ERROR << "Invalid image value."; return -1; } msg.container_name = d[msg::FLD_CONTAINER_NAME].as(); msg.pubkey = d[msg::FLD_PUBKEY].as(); msg.contract_id = d[msg::FLD_CONTRACT_ID].as(); msg.image = d[msg::FLD_IMAGE].as(); return 0; } /** * Extracts initiate message from msg. * @param msg Populated msg object. * @param d The json document holding the read request message. * Accepted signed input container format: * { * "type": "initiate", * "container_name": "", * "config": {---config overrides----} * } * @return 0 on successful extraction. -1 for failure. */ int extract_initiate_message(initiate_msg &msg, const jsoncons::json &d) { // Commented out when merging create and initiate messages. // if (extract_type(msg.type, d) == -1) // return -1; // if (!d.contains(msg::FLD_CONTAINER_NAME)) // { // LOG_ERROR << "Field container_name is missing."; // return -1; // } // if (!d[msg::FLD_CONTAINER_NAME].is()) // { // LOG_ERROR << "Invalid container_name value."; // return -1; // } // msg.container_name = d[msg::FLD_CONTAINER_NAME].as(); if (!d.contains(msg::FLD_CONFIG)) { LOG_ERROR << "Field config is missing."; return -1; } const jsoncons::json &config = d[msg::FLD_CONFIG]; if (config.contains(msg::FLD_MESH)) { const jsoncons::json &mesh = config[msg::FLD_MESH]; if (mesh.contains(msg::FLD_IDLE_TIMEOUT)) msg.config.mesh.idle_timeout = mesh[msg::FLD_IDLE_TIMEOUT].as(); if (mesh.contains(msg::FLD_KNOWN_PEERS)) { if (!mesh[msg::FLD_KNOWN_PEERS].empty() && !mesh[msg::FLD_KNOWN_PEERS].is_array()) { LOG_ERROR << "Invalid known_peers value."; return -1; } else if (!mesh[msg::FLD_KNOWN_PEERS].empty() && mesh[msg::FLD_KNOWN_PEERS].size() > 0) { std::vector splitted; for (auto &val : mesh[msg::FLD_KNOWN_PEERS].array_range()) { if (!val.is()) { LOG_ERROR << "Invalid peer value."; return -1; } const std::string peer = val.as(); util::split_string(splitted, peer, ":"); if (splitted.size() != 2) { LOG_ERROR << "Invalid peer value: " << peer; return -1; } uint16_t port; if (util::stoul(splitted.back(), port) == -1) { LOG_ERROR << "Invalid peer port value: " << peer; return -1; } msg.config.mesh.known_peers.emplace(conf::host_ip_port{splitted.front(), port}); splitted.clear(); } } } if (mesh.contains(msg::FLD_MSG_FORWARDING)) msg.config.mesh.msg_forwarding = mesh[msg::FLD_MSG_FORWARDING].as(); if (mesh.contains(msg::FLD_MAX_CONS)) msg.config.mesh.max_connections = mesh[msg::FLD_MAX_CONS].as(); if (mesh.contains(msg::FLD_MAX_KNOWN_CONS)) msg.config.mesh.max_known_connections = mesh[msg::FLD_MAX_KNOWN_CONS].as(); if (mesh.contains(msg::FLD_MAX_IN_CONS_HOST)) msg.config.mesh.max_in_connections_per_host = mesh[msg::FLD_MAX_IN_CONS_HOST].as(); if (mesh.contains(msg::FLD_MAX_BYTES_MSG)) msg.config.mesh.max_bytes_per_msg = mesh[msg::FLD_MAX_BYTES_MSG].as(); if (mesh.contains(msg::FLD_MAX_BYTES_MIN)) msg.config.mesh.max_bytes_per_min = mesh[msg::FLD_MAX_BYTES_MIN].as(); if (mesh.contains(msg::FLD_MAX_BAD_MSG_MIN)) msg.config.mesh.max_bad_msgs_per_min = mesh[msg::FLD_MAX_BAD_MSG_MIN].as(); if (mesh.contains(msg::FLD_MAX_BAD_MSG_SIG_MIN)) msg.config.mesh.max_bad_msgsigs_per_min = mesh[msg::FLD_MAX_BAD_MSG_SIG_MIN].as(); if (mesh.contains(msg::FLD_MAX_DUP_MSG_MIN)) msg.config.mesh.max_dup_msgs_per_min = mesh[msg::FLD_MAX_DUP_MSG_MIN].as(); if (mesh.contains(msg::FLD_PEER_DISCOVERY)) { const jsoncons::json &peer_discovery = mesh[msg::FLD_PEER_DISCOVERY]; if (peer_discovery.contains(msg::FLD_ENABLED)) msg.config.mesh.peer_discovery.enabled = peer_discovery[msg::FLD_ENABLED].as(); if (peer_discovery.contains(msg::FLD_INTERVAL)) msg.config.mesh.peer_discovery.interval = peer_discovery[msg::FLD_INTERVAL].as(); } } if (config.contains(msg::FLD_CONTRACT)) { const jsoncons::json &contract = config[msg::FLD_CONTRACT]; if (contract.contains(msg::FLD_UNL)) { if (!contract[msg::FLD_UNL].empty() && !contract[msg::FLD_UNL].is_array()) { LOG_ERROR << "Invalid unl value."; return -1; } else if (!contract[msg::FLD_UNL].empty() && contract[msg::FLD_UNL].size() > 0) { for (auto &val : contract[msg::FLD_UNL].array_range()) { if (!val.is()) { LOG_ERROR << "Invalid unl pubkey value."; return -1; } const std::string unl_pubkey = val.as(); const std::string unl_pubkey_bin = util::to_bin(unl_pubkey); if (unl_pubkey_bin.empty()) { LOG_ERROR << "Invalid unl pubkey value: " << unl_pubkey; return -1; } msg.config.contract.unl.emplace(unl_pubkey_bin); } } } if (contract.contains(msg::FLD_EXECUTE)) msg.config.contract.execute = contract[msg::FLD_EXECUTE].as(); if (contract.contains(msg::FLD_ROUNDTIME)) msg.config.contract.roundtime = contract[msg::FLD_ROUNDTIME].as(); if (contract.contains(msg::FLD_LOG)) { const jsoncons::json &log = contract[msg::FLD_LOG]; if (log.contains(msg::FLD_ENABLE)) msg.config.contract.log.enable = log[msg::FLD_ENABLE].as(); if (log.contains(msg::FLD_MAX_MB_PER_FILE)) msg.config.contract.log.max_mbytes_per_file = log[msg::FLD_MAX_MB_PER_FILE].as(); if (log.contains(msg::FLD_MAX_FILE_COUNT)) msg.config.contract.log.max_file_count = log[msg::FLD_MAX_FILE_COUNT].as(); } } if (config.contains(msg::FLD_NODE)) { const jsoncons::json &node = config[msg::FLD_NODE]; if (node.contains(msg::FLD_ROLE)) { if (!node[msg::FLD_ROLE].is()) { LOG_ERROR << "Invalid role value."; return -1; } msg.config.node.role = node[msg::FLD_ROLE].as(); } if (node.contains(msg::FLD_HISTORY)) { if (!node[msg::FLD_HISTORY].is()) { LOG_ERROR << "Invalid history value."; return -1; } msg.config.node.history = node[msg::FLD_HISTORY].as(); } if (node.contains(msg::FLD_HISTORY_CONFIG)) { const jsoncons::json &history_config = node[msg::FLD_HISTORY_CONFIG]; if (history_config.contains(msg::FLD_MAX_P_SHARDS)) { if (!history_config[msg::FLD_MAX_P_SHARDS].empty() && !history_config[msg::FLD_MAX_P_SHARDS].is()) { LOG_ERROR << "Invalid max_primary_shards value."; return -1; } else if (!history_config[msg::FLD_MAX_P_SHARDS].empty()) msg.config.node.history_config.max_primary_shards = history_config[msg::FLD_MAX_P_SHARDS].as(); } if (history_config.contains(msg::FLD_MAX_R_SHARDS)) { if (!history_config[msg::FLD_MAX_R_SHARDS].empty() && !history_config[msg::FLD_MAX_R_SHARDS].is()) { LOG_ERROR << "Invalid max_raw_shards value."; return -1; } else if (!history_config[msg::FLD_MAX_R_SHARDS].empty()) msg.config.node.history_config.max_raw_shards = history_config[msg::FLD_MAX_R_SHARDS].as(); } } } if (config.contains(msg::FLD_USER)) { const jsoncons::json &user = config[msg::FLD_USER]; if (user.contains(msg::FLD_IDLE_TIMEOUT)) msg.config.user.idle_timeout = user[msg::FLD_IDLE_TIMEOUT].as(); if (user.contains(msg::FLD_MAX_BYTES_MSG)) msg.config.user.max_bytes_per_msg = user[msg::FLD_MAX_BYTES_MSG].as(); if (user.contains(msg::FLD_MAX_BYTES_MIN)) msg.config.user.max_bytes_per_min = user[msg::FLD_MAX_BYTES_MIN].as(); if (user.contains(msg::FLD_MAX_BAD_MSG_MIN)) msg.config.user.max_bad_msgs_per_min = user[msg::FLD_MAX_BAD_MSG_MIN].as(); if (user.contains(msg::FLD_MAX_CONS)) msg.config.user.max_connections = user[msg::FLD_MAX_CONS].as(); if (user.contains(msg::FLD_MAX_IN_CONS_HOST)) msg.config.user.max_in_connections_per_host = user[msg::FLD_MAX_IN_CONS_HOST].as(); if (user.contains(msg::FLD_CON_READ_REQ)) msg.config.user.concurrent_read_requests = user[msg::FLD_CON_READ_REQ].as(); } if (config.contains(msg::FLD_HPFS)) { const jsoncons::json &hpfs = config[msg::FLD_HPFS]; if (hpfs.contains(msg::FLD_LOG) && hpfs[msg::FLD_LOG].contains(msg::FLD_LOG_LEVEL)) msg.config.hpfs.log.log_level = hpfs[msg::FLD_LOG][msg::FLD_LOG_LEVEL].as(); } if (config.contains(msg::FLD_LOG)) { const jsoncons::json &log = config[msg::FLD_LOG]; if (log.contains(msg::FLD_LOG_LEVEL)) msg.config.log.log_level = log[msg::FLD_LOG_LEVEL].as(); if (log.contains(msg::FLD_MAX_MB_PER_FILE)) msg.config.log.max_mbytes_per_file = log[msg::FLD_MAX_MB_PER_FILE].as(); if (log.contains(msg::FLD_MAX_FILE_COUNT)) msg.config.log.max_file_count = log[msg::FLD_MAX_FILE_COUNT].as(); if (log.contains(msg::FLD_LOGGERS)) { if (!log[msg::FLD_LOGGERS].empty() && !log[msg::FLD_LOGGERS].is_array()) { LOG_ERROR << "Invalid loggers value."; return -1; } else if (!log[msg::FLD_LOGGERS].empty() && log[msg::FLD_LOGGERS].size() > 0) { for (auto &val : log[msg::FLD_LOGGERS].array_range()) { if (!val.is()) { LOG_ERROR << "Invalid log value."; return -1; } msg.config.log.loggers.emplace(val.as()); } } } } return 0; } /** * Extracts destroy message from msg. * @param msg Populated msg object. * @param d The json document holding the read request message. * Accepted signed input container format: * { * "type": "destroy", * "owner_pubkey": "", * "container_name": "", * } * @return 0 on successful extraction. -1 for failure. */ int extract_destroy_message(destroy_msg &msg, const jsoncons::json &d) { if (extract_type(msg.type, d) == -1) return -1; if (!d.contains(msg::FLD_CONTAINER_NAME)) { LOG_ERROR << "Field container_name is missing."; return -1; } if (!d[msg::FLD_CONTAINER_NAME].is()) { LOG_ERROR << "Invalid container_name value."; return -1; } msg.container_name = d[msg::FLD_CONTAINER_NAME].as(); return 0; } /** * Extracts start message from msg. * @param msg Populated msg object. * @param d The json document holding the read request message. * Accepted signed input container format: * { * "type": "start", * "owner_pubkey": "", * "container_name": "", * } * @return 0 on successful extraction. -1 for failure. */ int extract_start_message(start_msg &msg, const jsoncons::json &d) { if (extract_type(msg.type, d) == -1) return -1; if (!d.contains(msg::FLD_CONTAINER_NAME)) { LOG_ERROR << "Field container_name is missing."; return -1; } if (!d[msg::FLD_CONTAINER_NAME].is()) { LOG_ERROR << "Invalid container_name value."; return -1; } msg.container_name = d[msg::FLD_CONTAINER_NAME].as(); return 0; } /** * Extracts stop message from msg. * @param msg Populated msg object. * @param d The json document holding the read request message. * Accepted signed input container format: * { * "type": "stop", * "owner_pubkey": "", * "container_name": "", * } * @return 0 on successful extraction. -1 for failure. */ int extract_stop_message(stop_msg &msg, const jsoncons::json &d) { if (extract_type(msg.type, d) == -1) return -1; if (!d.contains(msg::FLD_CONTAINER_NAME)) { LOG_ERROR << "Field container_name is missing."; return -1; } if (!d[msg::FLD_CONTAINER_NAME].is()) { LOG_ERROR << "Invalid container_name value."; return -1; } msg.container_name = d[msg::FLD_CONTAINER_NAME].as(); return 0; } /** * Extracts inspect message from msg. * @param msg Populated msg object. * @param d The json document holding the message. * Accepted signed input container format: * { * "type": "inspect", * "container_name": "", * } * @return 0 on successful extraction. -1 for failure. */ int extract_inspect_message(inspect_msg &msg, const jsoncons::json &d) { if (extract_type(msg.type, d) == -1) return -1; if (!d.contains(msg::FLD_CONTAINER_NAME)) { LOG_ERROR << "Field container_name is missing."; return -1; } if (!d[msg::FLD_CONTAINER_NAME].is()) { LOG_ERROR << "Invalid container_name value."; return -1; } msg.container_name = d[msg::FLD_CONTAINER_NAME].as(); return 0; } /** * Constructs a generic json response. * @param msg Buffer to construct the generated json message string into. * Message format: * { * 'type': '', * "content": "" * } * @param response_type Type of the response. * @param content Content inside the response. * @param json_content Whether content is a json string. */ void build_response(std::string &msg, std::string_view response_type, std::string_view content, const bool json_content) { msg.reserve(1024); msg += "{\""; msg += msg::FLD_TYPE; msg += SEP_COLON; msg += response_type; msg += SEP_COMMA; msg += msg::FLD_CONTENT; msg += (json_content ? SEP_COLON_NOQUOTE : SEP_COLON); msg += content; msg += (json_content ? "}" : "\"}"); } /** * Constructs a json response for create message. * @param msg Buffer to construct the generated json message string into. * Message format: * { * "name": "" * "username": """ * "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) { msg.reserve(1024); msg += "{\""; msg += "name"; msg += SEP_COLON; msg += info.container_name; msg += SEP_COMMA; // msg += "username"; // Uncomment if username is required for debugging. // msg += SEP_COLON; // msg += info.username; // 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.assigned_ports.peer_port); msg += SEP_COMMA; msg += "user_port"; msg += SEP_COLON; msg += std::to_string(info.assigned_ports.user_port); msg += "\"}"; } /** * Constructs the response message for list message. * @param msg Buffer to construct the generated json message string into. * Message format: * [ * { * "name": "", * "user": "", * "image": "", * "status": "", * "peer_port": "", * "user_port": "", * "created_timestamp": , * "contract_id": "", * "expiry_approx_timestamp": , * "created_ledger": , * "expiry_ledger": , * "tenant": "", * } * ] * @param instances Instance list. * */ void build_list_response(std::string &msg, const std::vector &instances, const std::vector &leases) { msg.reserve(1024); msg += "["; for (size_t i = 0; i < instances.size(); i++) { const hp::instance_info &instance = instances[i]; msg += "{\""; msg += "name"; msg += SEP_COLON; msg += instance.container_name; msg += SEP_COMMA; msg += "user"; msg += SEP_COLON; msg += instance.username; msg += SEP_COMMA; msg += "image"; msg += SEP_COLON; msg += instance.image_name; msg += SEP_COMMA; msg += "contract_id"; msg += SEP_COLON; msg += instance.contract_id; msg += SEP_COMMA; msg += "status"; msg += SEP_COLON; msg += instance.status; msg += SEP_COMMA; msg += "peer_port"; msg += SEP_COLON_NOQUOTE; msg += std::to_string(instance.assigned_ports.peer_port); msg += SEP_COMMA_NOQUOTE; msg += "user_port"; msg += SEP_COLON_NOQUOTE; msg += std::to_string(instance.assigned_ports.user_port); // Include matching lease information. const auto lease = std::find_if(leases.begin(), leases.end(), [&](const hp::lease_info &l) { return l.container_name == instance.container_name; }); if (lease != leases.end()) { msg += SEP_COMMA_NOQUOTE; msg += "created_timestamp"; msg += SEP_COLON_NOQUOTE; msg += std::to_string(lease->timestamp); msg += SEP_COMMA_NOQUOTE; msg += "created_ledger"; msg += SEP_COLON_NOQUOTE; msg += std::to_string(lease->created_on_ledger); msg += SEP_COMMA_NOQUOTE; msg += "expiry_ledger"; msg += SEP_COLON_NOQUOTE; msg += std::to_string(lease->created_on_ledger + (lease->life_moments * MOMENT_SIZE)); msg += SEP_COMMA_NOQUOTE; msg += "tenant"; msg += SEP_COLON; msg += lease->tenant_xrp_address; msg += "\""; } msg += "}"; if (i < instances.size() - 1) msg += ","; } msg += "]"; std::cout << msg << "\n"; } /** * Constructs the response message for inspect message. * @param msg Buffer to construct the generated json message string into. * Message format: * { * "name": "", * "user": "", * "image": "", * "status": "", * "peer_port": "", * "user_port": "" * } * @param instance Instance info. * */ void build_inspect_response(std::string &msg, const hp::instance_info &instance) { msg.reserve(1024); msg += "{\""; msg += "name"; msg += SEP_COLON; msg += instance.container_name; msg += SEP_COMMA; msg += "user"; msg += SEP_COLON; msg += instance.username; msg += SEP_COMMA; msg += "image"; msg += SEP_COLON; msg += instance.image_name; msg += SEP_COMMA; msg += "status"; msg += SEP_COLON; msg += instance.status; msg += SEP_COMMA; msg += "peer_port"; msg += SEP_COLON_NOQUOTE; msg += std::to_string(instance.assigned_ports.peer_port); msg += SEP_COMMA_NOQUOTE; msg += "user_port"; msg += SEP_COLON_NOQUOTE; msg += std::to_string(instance.assigned_ports.user_port); msg += "}"; } } // namespace msg::json