From 39031c3157753367ae3b8604bdcebaba1743090f Mon Sep 17 00:00:00 2001 From: Ravin Perera <33562092+ravinsp@users.noreply.github.com> Date: Mon, 11 Nov 2019 15:06:25 +0530 Subject: [PATCH] User control messages. (#56) --- examples/hpclient/client.js | 47 +++++++----- examples/hpclient/malicious-client.js | 3 +- src/bill/corebill.cpp | 4 +- src/cons/cons.cpp | 13 +++- src/cons/cons.hpp | 7 -- src/jsonschema/usrmsg_helpers.cpp | 104 +++++++++++++++++++++----- src/jsonschema/usrmsg_helpers.hpp | 27 +++---- src/proc/ptrace_capture.cpp | 2 +- src/usr/usr.cpp | 12 ++- src/util.hpp | 8 +- 10 files changed, 155 insertions(+), 72 deletions(-) diff --git a/examples/hpclient/client.js b/examples/hpclient/client.js index 3af59f7d..1d89a542 100644 --- a/examples/hpclient/client.js +++ b/examples/hpclient/client.js @@ -54,6 +54,29 @@ function main() { ws.close() }); + function create_input_container(inp) { + let inp_container = { + nonce: (new Date()).getTime().toString(), + input: Buffer.from(inp).toString('hex'), + max_ledger_seqno: 9999999 + } + let inp_container_bytes = JSON.stringify(inp_container); + let sig_bytes = sodium.crypto_sign_detached(inp_container_bytes, keys.privateKey); + + let signed_inp_container = { + type: "contract_input", + content: inp_container_bytes.toString('hex'), + sig: Buffer.from(sig_bytes).toString('hex') + } + + return JSON.stringify(signed_inp_container); + } + + function create_status_request() { + let statreq = { type: 'stat' } + return JSON.stringify(statreq); + } + ws.on('message', (m) => { console.log("-----Received raw message-----") console.log(m.toString()) @@ -76,8 +99,7 @@ function main() { // sign the challenge and send back the response var sigbytes = sodium.crypto_sign_detached(m.challenge, keys.privateKey); var response = { - version: '0.1', - type: 'challenge_response', + type: 'challenge_resp', challenge: m.challenge, sig: Buffer.from(sigbytes).toString('hex'), pubkey: pkhex @@ -96,22 +118,13 @@ function main() { var input_pump = () => { rl.question('\nProvide an input: ', (inp) => { - let inp_container = { - nonce: (new Date()).getTime().toString(), - input: Buffer.from(inp).toString('hex'), - maxledgerseqno: 9999999 - } - let inp_container_bytes = JSON.stringify(inp_container); - let sig_bytes = sodium.crypto_sign_detached(inp_container_bytes, keys.privateKey); + let msgtosend = ""; - let signed_inp_container = { - version: "0.1", - type: "contract_input", - content: inp_container_bytes.toString('hex'), - sig: Buffer.from(sig_bytes).toString('hex') - } - - let msgtosend = JSON.stringify(signed_inp_container); + if (inp == "stat") + msgtosend = create_status_request(); + else + msgtosend = create_input_container(inp); + console.log("Sending message: " + msgtosend); ws.send(msgtosend) diff --git a/examples/hpclient/malicious-client.js b/examples/hpclient/malicious-client.js index ab84cf7a..2c81e665 100644 --- a/examples/hpclient/malicious-client.js +++ b/examples/hpclient/malicious-client.js @@ -83,8 +83,7 @@ function main() { // sign the challenge and send back the response var sigbytes = sodium.crypto_sign_detached(m.challenge, keys.privateKey); var response = { - version: '0.1', - type: 'challenge_response', + type: 'challenge_resp', challenge: m.challenge, sig: Buffer.from(sigbytes).toString('hex'), pubkey: pkhex diff --git a/src/bill/corebill.cpp b/src/bill/corebill.cpp index 9847c5bc..27b4b1f3 100644 --- a/src/bill/corebill.cpp +++ b/src/bill/corebill.cpp @@ -7,10 +7,10 @@ namespace corebill { // How many violations can occur for a host before being escalated. -static const uint32_t VIOLATION_THRESHOLD = 10; +constexpr uint32_t VIOLATION_THRESHOLD = 10; // Violation cooldown interval. -static const uint32_t VIOLATION_REFRESH_INTERVAL = 600 * 1000; // 10 minutes +constexpr uint32_t VIOLATION_REFRESH_INTERVAL = 600 * 1000; // 10 minutes // Keeps track of violation count against offending hosts. std::unordered_map violation_counter; diff --git a/src/cons/cons.cpp b/src/cons/cons.cpp index bcf6daae..57be03a1 100644 --- a/src/cons/cons.cpp +++ b/src/cons/cons.cpp @@ -20,6 +20,13 @@ namespace jusrmsg = jsonschema::usrmsg; namespace cons { +/** + * Voting thresholds for consensus stages. + */ +constexpr float STAGE1_THRESHOLD = 0.5; +constexpr float STAGE2_THRESHOLD = 0.65; +constexpr float STAGE3_THRESHOLD = 0.8; + consensus_context ctx; int init() @@ -574,10 +581,12 @@ void dispatch_user_outputs(const p2p::proposal &cons_prop) { std::string outputtosend; outputtosend.swap(cand_output.output); - usr::user_outbound_message outmsg(std::move(outputtosend)); + + std::string msg; + jusrmsg::create_contract_output_container(msg, outputtosend); const usr::connected_user &user = user_itr->second; - user.session->send(std::move(outmsg)); + user.session->send(usr::user_outbound_message(std::move(msg))); } } } diff --git a/src/cons/cons.hpp b/src/cons/cons.hpp index 18aab271..6a50b70a 100644 --- a/src/cons/cons.hpp +++ b/src/cons/cons.hpp @@ -10,13 +10,6 @@ namespace cons { -//stage 1 vote threshold -static const float STAGE1_THRESHOLD = 0.5; -//stage 2 vote threshold -static const float STAGE2_THRESHOLD = 0.65; -//stage 3 vote threshold -static const float STAGE3_THRESHOLD = 0.8; - /** * Represents a contract input that takes part in consensus. */ diff --git a/src/jsonschema/usrmsg_helpers.cpp b/src/jsonschema/usrmsg_helpers.cpp index ecd1bd06..172818d1 100644 --- a/src/jsonschema/usrmsg_helpers.cpp +++ b/src/jsonschema/usrmsg_helpers.cpp @@ -1,18 +1,37 @@ #include "../pchheader.hpp" #include "../util.hpp" #include "../crypto.hpp" +#include "../cons/cons.hpp" #include "../hplog.hpp" #include "usrmsg_helpers.hpp" namespace jsonschema::usrmsg { +// User JSON message schema version +constexpr const char* SCHEMA_VERSION = "0.1"; + // Separators -static const char *SEP_COMMA = "\",\""; -static const char *SEP_COLON = "\":\""; +constexpr const char* SEP_COMMA = "\",\""; +constexpr const char* SEP_COLON = "\":\""; +constexpr const char* SEP_COMMA_NOQUOTE = ",\""; +constexpr const char* SEP_COLON_NOQUOTE = "\":"; + +// Message field names +const char* const FLD_VERSION = "version"; +constexpr const char* FLD_TYPE = "type"; +constexpr const char* FLD_CHALLENGE = "challenge"; +constexpr const char* FLD_SIG = "sig"; +constexpr const char* FLD_PUBKEY = "pubkey"; +constexpr const char* FLD_INPUT = "input"; +constexpr const char* FLD_MAX_LED_SEQ = "max_ledger_seqno"; +constexpr const char* FLD_CONTENT = "content"; +constexpr const char* FLD_NONCE = "nonce"; +constexpr const char* FLD_LCL = "lcl"; +constexpr const char* FLD_LCL_SEQ = "lcl_seqno"; // Length of user random challenge bytes. -static const size_t CHALLENGE_LEN = 16; +const size_t CHALLENGE_LEN = 16; /** * Constructs user challenge message json and the challenge string required for @@ -43,8 +62,8 @@ void create_user_challenge(std::string &msg, std::string &challengehex) // We do not use RapidJson here in favour of performance because this is a simple json message. // Since we know the rough size of the challenge massage we reserve adequate amount for the holder. - // Only Hot Pocket version number is variable length. Therefore message size is roughly 95 bytes - // so allocating 128bits for heap padding. + // Only Hot Pocket version number is variable length. Therefore message size is roughly 90 bytes + // so allocating 128bytes for heap padding. msg.reserve(128); msg.append("{\"") .append(FLD_VERSION) @@ -61,6 +80,55 @@ void create_user_challenge(std::string &msg, std::string &challengehex) .append("\"}"); } +/** + * Constructs a status response message. + * @param msg String reference to copy the generated json message string into. + */ +void create_status_response(std::string &msg) +{ + msg.reserve(128); + msg.append("{\"") + .append(FLD_TYPE) + .append(SEP_COLON) + .append(MSGTYPE_STAT_RESP) + .append(SEP_COMMA) + .append(FLD_LCL) + .append(SEP_COLON) + .append(cons::ctx.lcl) + .append(SEP_COMMA) + .append(FLD_LCL_SEQ) + .append(SEP_COLON_NOQUOTE) + .append(std::to_string(cons::ctx.led_seq_no)) + .append("}"); +} + +/** + * Constructs a contract output container message. + * @param msg String reference to copy the generated json message string into. + * @param content The contract output content to be put in the message. + */ +void create_contract_output_container(std::string &msg, std::string_view content) +{ + msg.reserve(128); + msg.append("{\"") + .append(FLD_TYPE) + .append(SEP_COLON) + .append(MSGTYPE_CONTRACT_OUTPUT) + .append(SEP_COMMA) + .append(FLD_LCL) + .append(SEP_COLON) + .append(cons::ctx.lcl) + .append(SEP_COMMA) + .append(FLD_LCL_SEQ) + .append(SEP_COLON_NOQUOTE) + .append(std::to_string(cons::ctx.led_seq_no)) + .append(SEP_COMMA_NOQUOTE) + .append(FLD_CONTENT) + .append(SEP_COLON) + .append(content) + .append("\"}"); +} + /** * Verifies the user challenge response with the original challenge issued to the user * and the user public key contained in the response. @@ -69,8 +137,7 @@ void create_user_challenge(std::string &msg, std::string &challengehex) * @param response The response bytes to verify. This will be parsed as json. * Accepted response format: * { - * "version": "" - * "type": "challenge_response", + * "type": "challenge_resp", * "challenge": "", * "sig": "", * "pubkey": "" @@ -136,7 +203,6 @@ int verify_user_challenge_response(std::string &extracted_pubkeyhex, std::string * @param d The json document holding the input container. * Accepted signed input container format: * { - * "version": "" * "type": "contract_input", * "content": "", * "sig": "" @@ -176,12 +242,12 @@ int extract_signed_input_container( * Extract the individual components of a given input container json. * @param nonce The extracted nonce. * @param input The extracted input. - * @param max_ledger_seqno The extracted max ledger sequence no. + * @param max_ledger_seqno Themaxledgerseqno extracted max ledger sequence no. * @param contentjson The json string containing the input container message. * { * "nonce": "", * "input": "", - * "maxledgerseqno": 4562712334 + * "max_ledger_seqno": 4562712334 * } * @return 0 on succesful extraction. -1 on failure. */ @@ -213,9 +279,9 @@ int extract_input_container(std::string &nonce, std::string &input, uint64_t &ma // Convert hex input to binary. input.resize(inputhex.length() / 2); if (util::hex2bin( - reinterpret_cast(input.data()), - input.length(), - inputhex) != 0) + reinterpret_cast(input.data()), + input.length(), + inputhex) != 0) { LOG_DBG << "Contract input format invalid."; return -1; @@ -231,6 +297,11 @@ int extract_input_container(std::string &nonce, std::string &input, uint64_t &ma * Parses a json message sent by a user. * @param d RapidJson 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(rapidjson::Document &d, std::string_view message) @@ -245,13 +316,6 @@ int parse_user_message(rapidjson::Document &d, std::string_view message) return -1; } - // Check existence of msg type field. - if (!d.HasMember(FLD_VERSION) || !d[FLD_VERSION].IsString()) - { - LOG_DBG << "User json message 'version' missing or invalid."; - return -1; - } - // Check existence of msg type field. if (!d.HasMember(FLD_TYPE) || !d[FLD_TYPE].IsString()) { diff --git a/src/jsonschema/usrmsg_helpers.hpp b/src/jsonschema/usrmsg_helpers.hpp index 5a7758b4..9dd612de 100644 --- a/src/jsonschema/usrmsg_helpers.hpp +++ b/src/jsonschema/usrmsg_helpers.hpp @@ -6,26 +6,23 @@ namespace jsonschema::usrmsg { -static const char *SCHEMA_VERSION = "0.1"; - -// These fields are used on json messages response validation. -static const char *FLD_VERSION = "version"; -static const char *FLD_TYPE = "type"; -static const char *FLD_CHALLENGE = "challenge"; -static const char *FLD_SIG = "sig"; -static const char *FLD_PUBKEY = "pubkey"; -static const char *FLD_INPUT = "input"; -static const char *FLD_MAX_LED_SEQ = "maxledgerseqno"; -static const char *FLD_CONTENT = "content"; -static const char *FLD_NONCE = "nonce"; +// Message field names exposed out of this namespace. +extern const char* const FLD_TYPE; // Message types -static const char *MSGTYPE_CHALLENGE = "public_challenge"; -static const char *MSGTYPE_CHALLENGE_RESP = "challenge_response"; -static const char *MSGTYPE_CONTRACT_INPUT = "contract_input"; +constexpr const char* MSGTYPE_CHALLENGE = "public_challenge"; +constexpr const char* MSGTYPE_CHALLENGE_RESP = "challenge_resp"; +constexpr const char* MSGTYPE_CONTRACT_INPUT = "contract_input"; +constexpr const char* MSGTYPE_CONTRACT_OUTPUT = "contract_output"; +constexpr const char* MSGTYPE_STAT = "stat"; +constexpr const char* MSGTYPE_STAT_RESP = "stat_resp"; void create_user_challenge(std::string &msg, std::string &challengehex); +void create_status_response(std::string &msg); + +void create_contract_output_container(std::string &msg, std::string_view content); + int verify_user_challenge_response(std::string &extracted_pubkeyhex, std::string_view response, std::string_view original_challenge); int extract_signed_input_container(std::string &extracted_content, std::string &extracted_sig, const rapidjson::Document &d); diff --git a/src/proc/ptrace_capture.cpp b/src/proc/ptrace_capture.cpp index c304d4d7..13476b46 100644 --- a/src/proc/ptrace_capture.cpp +++ b/src/proc/ptrace_capture.cpp @@ -17,7 +17,7 @@ struct fd_info }; // File modifications are tracked in 4MB blocks. -static const int BLOCK_SIZE = 4 * 1024 * 1024; +constexpr int BLOCK_SIZE = 4 * 1024 * 1024; /** * Blocks the calling thread and captures the child process activity until it exits. diff --git a/src/usr/usr.cpp b/src/usr/usr.cpp index 9616916a..12734f42 100644 --- a/src/usr/usr.cpp +++ b/src/usr/usr.cpp @@ -88,7 +88,7 @@ int verify_challenge(std::string_view message, sock::socket_sessionuniqueid); // Remove the stored challenge LOG_DBG << "User connection " << session->uniqueid << " authenticated. Public key " - << userpubkeyhex; + << userpubkeyhex; return 0; } else @@ -123,7 +123,7 @@ int handle_user_message(connected_user &user, std::string_view message) if (jusrmsg::extract_signed_input_container(contentjson, sig, d) == 0) { std::lock_guard lock(ctx.users_mutex); - + //Add to the submitted input list. user.submitted_inputs.push_back(user_submitted_message( std::move(contentjson), @@ -131,6 +131,14 @@ int handle_user_message(connected_user &user, std::string_view message) return 0; } } + else if (d[jusrmsg::FLD_TYPE] == jusrmsg::MSGTYPE_STAT) + { + std::string msg; + LOG_DBG << msg; + jusrmsg::create_status_response(msg); + user.session->send(user_outbound_message(std::move(msg))); + return 0; + } else { LOG_DBG << "Invalid user message type: " << d[jusrmsg::FLD_TYPE].GetString(); diff --git a/src/util.hpp b/src/util.hpp index bc72b26e..555687e7 100644 --- a/src/util.hpp +++ b/src/util.hpp @@ -11,17 +11,17 @@ namespace util { // Hot Pocket version. Displayed on 'hotpocket version' and written to new contract configs. -static const char *HP_VERSION = "0.1"; +constexpr const char* HP_VERSION = "0.1"; // Minimum compatible contract config version (this will be used to validate contract configs) -static const char *MIN_CONTRACT_VERSION = "0.1"; +constexpr const char* MIN_CONTRACT_VERSION = "0.1"; // Current version of the peer message protocol. -static const uint8_t PEERMSG_VERSION = 1; +constexpr uint8_t PEERMSG_VERSION = 1; // Minimum compatible peer message version (this will be used to accept/reject incoming peer connections) // (Keeping this as int for effcient msg payload and comparison) -static const uint8_t MIN_PEERMSG_VERSION = 1; +constexpr uint8_t MIN_PEERMSG_VERSION = 1; /** * FIFO hash set with a max size.