diff --git a/CMakeLists.txt b/CMakeLists.txt index 69fa110d..50ac2727 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,8 @@ add_executable(hpcore src/msg/fbuf/common_helpers.cpp src/msg/fbuf/p2pmsg_helpers.cpp src/msg/fbuf/ledger_helpers.cpp + src/msg/json/controlmsg_json.cpp + src/msg/controlmsg_parser.cpp src/msg/json/usrmsg_json.cpp src/msg/bson/usrmsg_bson.cpp src/msg/usrmsg_parser.cpp diff --git a/examples/c_contract/hotpocket_contract.h b/examples/c_contract/hotpocket_contract.h index 500c5691..11b8a42f 100644 --- a/examples/c_contract/hotpocket_contract.h +++ b/examples/c_contract/hotpocket_contract.h @@ -196,7 +196,7 @@ int hp_init(hp_contract_func contract_func) __hp_free_contract_context(&ctx); // Send termination control message. - write(gctx.control_fd, "Terminated", 10); + write(gctx.control_fd, "{\"type\":\"contract_end\"}", 24); close(gctx.control_fd); return 0; } diff --git a/examples/nodejs_contract/hp-contract-lib.js b/examples/nodejs_contract/hp-contract-lib.js index a66c6f39..1688c399 100644 --- a/examples/nodejs_contract/hp-contract-lib.js +++ b/examples/nodejs_contract/hp-contract-lib.js @@ -40,7 +40,7 @@ class HotPocketContract { } #terminate = () => { - this.#controlChannel.send("Terminated") + this.#controlChannel.send(JSON.stringify({ type: "contract_end" })); this.#controlChannel.close(); } } diff --git a/src/msg/controlmsg_common.hpp b/src/msg/controlmsg_common.hpp new file mode 100644 index 00000000..48c85001 --- /dev/null +++ b/src/msg/controlmsg_common.hpp @@ -0,0 +1,16 @@ +#ifndef _HP_MSG_CONTROLMSG_COMMON_ +#define _HP_MSG_CONTROLMSG_COMMON_ + +#include "../pchheader.hpp" + +namespace msg::controlmsg +{ + // Message field names + constexpr const char *FLD_TYPE = "type"; + + // Message types + constexpr const char *MSGTYPE_CONTRACT_END = "contract_end"; + +} // namespace msg::controlmsg + +#endif \ No newline at end of file diff --git a/src/msg/controlmsg_parser.cpp b/src/msg/controlmsg_parser.cpp new file mode 100644 index 00000000..aa019382 --- /dev/null +++ b/src/msg/controlmsg_parser.cpp @@ -0,0 +1,19 @@ +#include "../pchheader.hpp" +#include "json/controlmsg_json.hpp" +#include "controlmsg_parser.hpp" + +namespace jctlmsg = msg::controlmsg::json; + +namespace msg::controlmsg +{ + int controlmsg_parser::parse(std::string_view message) + { + return jctlmsg::parse_control_message(jsonDoc, message); + } + + int controlmsg_parser::extract_type(std::string &extracted_type) const + { + return jctlmsg::extract_type(extracted_type, jsonDoc); + } + +} // namespace msg::controlmsg \ No newline at end of file diff --git a/src/msg/controlmsg_parser.hpp b/src/msg/controlmsg_parser.hpp new file mode 100644 index 00000000..1fdb6888 --- /dev/null +++ b/src/msg/controlmsg_parser.hpp @@ -0,0 +1,20 @@ +#ifndef _HP_MSG_CONTROLMSG_PARSER_ +#define _HP_MSG_CONTROLMSG_PARSER_ + +#include "../pchheader.hpp" + +namespace msg::controlmsg +{ + class controlmsg_parser + { + jsoncons::json jsonDoc; + + public: + int parse(std::string_view message); + + int extract_type(std::string &extracted_type) const; + }; + +} // namespace msg::controlmsg + +#endif \ No newline at end of file diff --git a/src/msg/json/controlmsg_json.cpp b/src/msg/json/controlmsg_json.cpp new file mode 100644 index 00000000..b08df865 --- /dev/null +++ b/src/msg/json/controlmsg_json.cpp @@ -0,0 +1,58 @@ +#include "../../pchheader.hpp" +#include "../controlmsg_common.hpp" +#include "controlmsg_json.hpp" + +namespace msg::controlmsg::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 = "\":"; + + // Message types + constexpr const char *MSGTYPE_HANDSHAKE_CHALLENGE = "handshake_challenge"; + + /** + * Parses a json control message sent by the contract. + * @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_control_message(jsoncons::json &d, std::string_view message) + { + try + { + d = jsoncons::json::parse(message, jsoncons::strict_json_parsing()); + } + catch (const std::exception &e) + { + LOG_DEBUG << "User json message parsing failed."; + return -1; + } + + // Check existence of msg type field. + if (!d.contains(msg::controlmsg::FLD_TYPE) || !d[msg::controlmsg::FLD_TYPE].is()) + { + LOG_DEBUG << "User json message 'type' missing or invalid."; + return -1; + } + + return 0; + } + + /** + * Extracts the message 'type' value from the json document. + */ + int extract_type(std::string &extracted_type, const jsoncons::json &d) + { + extracted_type = d[msg::controlmsg::FLD_TYPE].as(); + return 0; + } + +} // namespace msg::controlmsg::json \ No newline at end of file diff --git a/src/msg/json/controlmsg_json.hpp b/src/msg/json/controlmsg_json.hpp new file mode 100644 index 00000000..550762a7 --- /dev/null +++ b/src/msg/json/controlmsg_json.hpp @@ -0,0 +1,17 @@ +#ifndef _HP_MSG_JSON_CONTROLMSG_JSON_ +#define _HP_MSG_JSON_CONTROLMSG_JSON_ + +#include "../../pchheader.hpp" + +/** + * Parser helpers for smart contract control messages. + */ +namespace msg::controlmsg::json +{ + int parse_control_message(jsoncons::json &d, std::string_view message); + + int extract_type(std::string &extracted_type, const jsoncons::json &d); + +} // namespace msg::controlmsg::json + +#endif \ No newline at end of file diff --git a/src/msg/json/usrmsg_json.cpp b/src/msg/json/usrmsg_json.cpp index 42de0417..66a37b73 100644 --- a/src/msg/json/usrmsg_json.cpp +++ b/src/msg/json/usrmsg_json.cpp @@ -21,18 +21,18 @@ namespace msg::usrmsg::json } /** - * Constructs user challenge message json and the challenge string required for - * initial user challenge handshake. This gets called when a user establishes - * a web socket connection to HP. - * - * @param msg String reference to copy the generated json message string into. - * Message format: - * { - * "type": "handshake_challenge", - * "challenge": "" - * } - * @param challengehex String reference to copy the generated hex challenge string into. - */ + * Constructs user challenge message json and the challenge string required for + * initial user challenge handshake. This gets called when a user establishes + * a web socket connection to HP. + * + * @param msg String reference to copy the generated json message string into. + * Message format: + * { + * "type": "handshake_challenge", + * "challenge": "" + * } + * @param challengehex String reference to copy the generated hex challenge string into. + */ void create_user_challenge(std::vector &msg, std::string &challengehex) { // Use libsodium to generate the random challenge bytes. @@ -63,15 +63,15 @@ namespace msg::usrmsg::json } /** - * Constructs a status response message. - * @param msg String reference to copy the generated json message string into. - * Message format: - * { - * "type": "stat_response", - * "lcl": "", - * "lcl_seqno": - * } - */ + * Constructs a status response message. + * @param msg String reference to copy the generated json message string into. + * Message format: + * { + * "type": "stat_response", + * "lcl": "", + * "lcl_seqno": + * } + */ void create_status_response(std::vector &msg, const uint64_t lcl_seq_no, std::string_view lcl) { msg.reserve(128); @@ -91,19 +91,19 @@ namespace msg::usrmsg::json } /** - * Constructs a contract input status message. - * @param msg String reference to copy the generated json message string into. - * Message format: - * { - * "type": "contract_input_status", - * "status": "", - * "reason": "", - * "input_sig": "" - * } - * @param is_accepted Whether the original message was accepted or not. - * @param reason Rejected reason. Empty if accepted. - * @param input_sig Binary signature of the original input message which generated this result. - */ + * Constructs a contract input status message. + * @param msg String reference to copy the generated json message string into. + * Message format: + * { + * "type": "contract_input_status", + * "status": "", + * "reason": "", + * "input_sig": "" + * } + * @param is_accepted Whether the original message was accepted or not. + * @param reason Rejected reason. Empty if accepted. + * @param input_sig Binary signature of the original input message which generated this result. + */ void create_contract_input_status(std::vector &msg, std::string_view status, std::string_view reason, std::string_view input_sig) { std::string sighex; @@ -130,15 +130,15 @@ namespace msg::usrmsg::json } /** - * Constructs a contract read response message. - * @param msg String reference to copy the generated json message string into. - * Message format: - * { - * "type": "contract_read_response", - * "content": "" - * } - * @param content The contract binary output content to be put in the message. - */ + * Constructs a contract read response message. + * @param msg String reference to copy the generated json message string into. + * Message format: + * { + * "type": "contract_read_response", + * "content": "" + * } + * @param content The contract binary output content to be put in the message. + */ void create_contract_read_response_container(std::vector &msg, std::string_view content) { std::string contenthex; @@ -160,17 +160,17 @@ namespace msg::usrmsg::json } /** - * Constructs a contract output container message. - * @param msg String reference to copy the generated json message string into. - * Message format: - * { - * "type": "contract_output", - * "lcl": "" - * "lcl_seqno": , - * "content": "" - * } - * @param content The contract binary output content to be put in the message. - */ + * Constructs a contract output container message. + * @param msg String reference to copy the generated json message string into. + * Message format: + * { + * "type": "contract_output", + * "lcl": "" + * "lcl_seqno": , + * "content": "" + * } + * @param content The contract binary output content to be put in the message. + */ void create_contract_output_container(std::vector &msg, std::string_view content, const uint64_t lcl_seq_no, std::string_view lcl) { std::string contenthex; @@ -200,23 +200,23 @@ namespace msg::usrmsg::json } /** - * Verifies the user handshake response with the original challenge issued to the user - * and the user public key contained in the response. - * - * @param extracted_pubkeyhex The hex public key extracted from the response. - * @param extracted_protocol The protocol code extracted from the response. - * @param response The response bytes to verify. This will be parsed as json. - * Accepted response format: - * { - * "type": "handshake_response", - * "challenge": "", - * "sig": "", - * "pubkey": "", - * "protocol": "" - * } - * @param original_challenge The original hex challenge string issued to the user. - * @return 0 if challenge response is verified. -1 if challenge not met or an error occurs. - */ + * Verifies the user handshake response with the original challenge issued to the user + * and the user public key contained in the response. + * + * @param extracted_pubkeyhex The hex public key extracted from the response. + * @param extracted_protocol The protocol code extracted from the response. + * @param response The response bytes to verify. This will be parsed as json. + * Accepted response format: + * { + * "type": "handshake_response", + * "challenge": "", + * "sig": "", + * "pubkey": "", + * "protocol": "" + * } + * @param original_challenge The original hex challenge string issued to the user. + * @return 0 if challenge response is verified. -1 if challenge not met or an error occurs. + */ int verify_user_handshake_response(std::string &extracted_pubkeyhex, std::string &extracted_protocol, std::string_view response, std::string_view original_challenge) { @@ -268,7 +268,7 @@ namespace msg::usrmsg::json } // Verify the challenge signature. We do this last due to signature verification cost. - std::string_view pubkeysv = d[msg::usrmsg::FLD_PUBKEY].as(); + std::string_view pubkeysv = d[msg::usrmsg::FLD_PUBKEY].as(); if (crypto::verify_hex( original_challenge, d[msg::usrmsg::FLD_SIG].as(), @@ -285,23 +285,23 @@ namespace msg::usrmsg::json } /** - * Parses a json message sent by a user. - * @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. - */ + * Parses a json message sent by a user. + * @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_user_message(jsoncons::json &d, std::string_view message) { try { d = jsoncons::json::parse(message, jsoncons::strict_json_parsing()); } - catch(const std::exception& e) + catch (const std::exception &e) { LOG_DEBUG << "User json message parsing failed."; return -1; @@ -318,8 +318,8 @@ namespace msg::usrmsg::json } /** - * Extracts the message 'type' value from the json document. - */ + * Extracts the message 'type' value from the json document. + */ int extract_type(std::string &extracted_type, const jsoncons::json &d) { extracted_type = d[msg::usrmsg::FLD_TYPE].as(); @@ -327,17 +327,17 @@ namespace msg::usrmsg::json } /** - * Extracts a contract read request message sent by user. - * - * @param extracted_content The content to be passed to the contract, extracted from the message. -* @param d The json document holding the read request message. - * Accepted signed input container format: - * { - * "type": "contract_read_request", - * "content": "" - * } - * @return 0 on successful extraction. -1 for failure. - */ + * Extracts a contract read request message sent by user. + * + * @param extracted_content The content to be passed to the contract, extracted from the message. + * @param d The json document holding the read request message. + * Accepted signed input container format: + * { + * "type": "contract_read_request", + * "content": "" + * } + * @return 0 on successful extraction. -1 for failure. + */ int extract_read_request(std::string &extracted_content, const jsoncons::json &d) { if (!d.contains(msg::usrmsg::FLD_CONTENT)) @@ -370,19 +370,19 @@ namespace msg::usrmsg::json } /** - * Extracts a signed input container message sent by user. - * - * @param extracted_input_container The input container extracted from the message. - * @param extracted_sig The binary signature extracted from the message. - * @param d The json document holding the input container. - * Accepted signed input container format: - * { - * "type": "contract_input", - * "input_container": "", - * "sig": "" - * } - * @return 0 on successful extraction. -1 for failure. - */ + * Extracts a signed input container message sent by user. + * + * @param extracted_input_container The input container extracted from the message. + * @param extracted_sig The binary signature extracted from the message. + * @param d The json document holding the input container. + * Accepted signed input container format: + * { + * "type": "contract_input", + * "input_container": "", + * "sig": "" + * } + * @return 0 on successful extraction. -1 for failure. + */ int extract_signed_input_container( std::string &extracted_input_container, std::string &extracted_sig, const jsoncons::json &d) { @@ -417,18 +417,18 @@ namespace msg::usrmsg::json } /** - * Extract the individual components of a given input container json. - * @param input The extracted input. - * @param nonce The extracted nonce. - * @param max_lcl_seqno The extracted max ledger sequence no. - * @param contentjson The json string containing the input container message. - * { - * "input": "", - * "nonce": "", - * "max_lcl_seqno": - * } - * @return 0 on succesful extraction. -1 on failure. - */ + * Extract the individual components of a given input container json. + * @param input The extracted input. + * @param nonce The extracted nonce. + * @param max_lcl_seqno The extracted max ledger sequence no. + * @param contentjson The json string containing the input container message. + * { + * "input": "", + * "nonce": "", + * "max_lcl_seqno": + * } + * @return 0 on succesful extraction. -1 on failure. + */ int extract_input_container(std::string &input, std::string &nonce, uint64_t &max_lcl_seqno, std::string_view contentjson) { jsoncons::json d; @@ -436,7 +436,7 @@ namespace msg::usrmsg::json { d = jsoncons::json::parse(contentjson, jsoncons::strict_json_parsing()); } - catch(const std::exception& e) + catch (const std::exception &e) { LOG_DEBUG << "User input container json parsing failed."; return -1; diff --git a/src/sc.cpp b/src/sc.cpp index 0348cec5..14d4bb0b 100644 --- a/src/sc.cpp +++ b/src/sc.cpp @@ -6,6 +6,8 @@ #include "sc.hpp" #include "hpfs/hpfs.hpp" #include "msg/fbuf/p2pmsg_helpers.hpp" +#include "msg/controlmsg_common.hpp" +#include "msg/controlmsg_parser.hpp" namespace sc { @@ -332,7 +334,7 @@ namespace sc while (!ctx.is_shutting_down) { // Atempt to read messages from contract (regardless of contract terminated or not). - const int hpsc_read_res = read_contract_hp_outputs(ctx); + const int hpsc_read_res = read_control_outputs(ctx); const int npl_read_res = ctx.args.readonly ? 0 : read_contract_npl_outputs(ctx); const int user_read_res = read_contract_fdmap_outputs(ctx.userfds, ctx.args.userbufs); @@ -351,13 +353,13 @@ namespace sc if (npl_write_res == -1) break; - const int hpsc_write_res = write_contract_hp_inputs(ctx); - if (hpsc_write_res == -1) + const int control_write_res = write_control_inputs(ctx); + if (control_write_res == -1) break; // If no operation was performed during this iteration, wait for a small delay until the next iteration. // This means there were no queued messages from either side. - if ((hpsc_read_res + npl_read_res + user_read_res + hpsc_write_res + hpsc_write_res) == 0) + if ((hpsc_read_res + npl_read_res + user_read_res + control_write_res + control_write_res) == 0) util::sleep(20); } @@ -398,7 +400,7 @@ namespace sc /** * Writes any hp input messages to the contract. */ - int write_contract_hp_inputs(execution_context &ctx) + int write_control_inputs(execution_context &ctx) { std::string control_msg; @@ -474,20 +476,20 @@ namespace sc * * @return 0 if no bytes were read. 1 if bytes were read.. */ - int read_contract_hp_outputs(execution_context &ctx) + int read_control_outputs(execution_context &ctx) { std::string output; - const int hpsc_res = read_iosocket(false, ctx.hpscfds, output); - if (hpsc_res == -1) + const int res = read_iosocket(false, ctx.hpscfds, output); + if (res == -1) { - LOG_ERROR << "Error reading HP output from the contract."; + LOG_ERROR << "Error reading control message from the contract."; } - else if (hpsc_res > 0) + else if (res > 0) { - handle_control_msgs(ctx, output); + handle_control_msg(ctx, output); } - return (hpsc_res > 0) ? 1 : 0; + return (res > 0) ? 1 : 0; } /** @@ -498,19 +500,19 @@ namespace sc int read_contract_npl_outputs(execution_context &ctx) { std::string output; - const int npl_res = read_iosocket(false, ctx.nplfds, output); + const int res = read_iosocket(false, ctx.nplfds, output); - if (npl_res == -1) + if (res == -1) { LOG_ERROR << "Error reading NPL output from the contract."; } - else if (npl_res > 0) + else if (res > 0) { // Broadcast npl messages once contract npl output is collected. broadcast_npl_output(output); } - return (npl_res > 0) ? 1 : 0; + return (res > 0) ? 1 : 0; } /** @@ -825,13 +827,17 @@ namespace sc ctx.is_shutting_down = true; } - void handle_control_msgs(execution_context &ctx, std::string &msg) + void handle_control_msg(execution_context &ctx, std::string_view msg) { - if (msg == "Terminated") + msg::controlmsg::controlmsg_parser parser; + std::string type; + if (parser.parse(msg) == -1 || parser.extract_type(type) == -1) + return; + + if (type == msg::controlmsg::MSGTYPE_CONTRACT_END) { ctx.termination_signaled = true; } - msg.clear(); } } // namespace sc diff --git a/src/sc.hpp b/src/sc.hpp index e0bf452f..314b55fa 100644 --- a/src/sc.hpp +++ b/src/sc.hpp @@ -151,11 +151,11 @@ namespace sc void contract_monitor_loop(execution_context &ctx); - int write_contract_hp_inputs(execution_context &ctx); + int write_control_inputs(execution_context &ctx); int write_npl_messages(execution_context &ctx); - int read_contract_hp_outputs(execution_context &ctx); + int read_control_outputs(execution_context &ctx); int read_contract_npl_outputs(execution_context &ctx); @@ -183,7 +183,7 @@ namespace sc void stop(execution_context &ctx); - void handle_control_msgs(execution_context &ctx, std::string &msg); + void handle_control_msg(execution_context &ctx, std::string_view msg); } // namespace sc