#include #include #include "../conf.hpp" #include "../crypto.hpp" #include "../util.hpp" #include "../hplog.hpp" #include "peer_message_handler.hpp" #include "message_content_generated.h" #include "message_container_generated.h" namespace p2p { /** * This section contains Flatbuffer message reading/writing helpers. * These helpers are mainly used by peer_session_handler. * * All Flatbuffer peer messages are 'Container' messages. 'Container' message is a bucket * which some common headers (version, singature etc..) and the message 'Content' (Proposal, NPL etc..). * * Therefore, when constructing peer messages, we have to first construct 'Content' message and then * place the 'Content' inside a 'Conatiner. 'Content' and 'Container' messages are constructed using * Flatbuffer builders. * * Reading is also 2 steps because of this. We have first interprit the 'Container' message from the * received data and then interprit the 'Content' portion of it separately to read the actual content. */ //---Message validation and reading helpers---/ /** * Verifies Conatiner message structure and outputs faltbuffer Container pointer to access the given buffer. * * @param container_ref A pointer reference to assign the pointer to the Container object. * @param container_bud The buffer containing the data that should validated and interpreted * via the container pointer. * @return 0 on successful verification. -1 for failure. */ int validate_and_extract_container(const Container **container_ref, std::string_view container_buf) { //Accessing message buffer const uint8_t *container_buf_ptr = reinterpret_cast(container_buf.data()); size_t container_buf_size = container_buf.length(); //Defining Flatbuffer verifier (default max depth = 64, max_tables = 1000000,) flatbuffers::Verifier container_verifier(container_buf_ptr, container_buf_size); //Verify container message using flatbuffer verifier if (!VerifyContainerBuffer(container_verifier)) { LOG_DBG << "Flatbuffer verify: Bad peer message container."; return -1; } //Get message container const Container *container = GetContainer(container_buf_ptr); //Validation are prioritzed base on expensiveness of validation. //i.e - signature validation is done at the end. //check protocol version of message whether it is greater than minimum supported protocol version. const uint16_t version = container->version(); if (version < util::MIN_PEERMSG_VERSION) { LOG_DBG << "Peer message is from unsupported protocol version (" << version << ")."; return -1; } int64_t time_now = util::get_epoch_milliseconds(); //check message timestamp. if (container->timestamp() < (time_now - conf::cfg.roundtime * 4)) { LOG_DBG << "Peer message is too old."; return -1; } // After signature is verified, get message hash and see wheteher // message is already recieved -> abandon if duplicate. // auto messageHash = crypto::sha_512_hash(message, "PEERMSG", 7); // if (recent_peer_msghash.count(messageHash) == 0) // { // recent_peer_msghash.try_emplace(std::move(messageHash), timestamp); // } // else // { // LOG_DBG << "Duplicate message"; // return -1; // } //Assign container and content out params. *container_ref = container; return 0; } /** * Validates the container message signing keys to see if the message is from a trusted source (UNL). * @return 0 on successful verification. -1 for failure. */ int validate_container_trust(const Container *container) { std::string_view msg_pubkey = flatbuff_bytes_to_sv(container->pubkey()); std::string_view msg_sig = flatbuff_bytes_to_sv(container->signature()); if (msg_pubkey.empty() || msg_sig.empty()) { LOG_DBG << "Peer message key pair incomplete. Trust verification failed."; return -1; } //validate if the message is not from a node of current node's unl list. if (!conf::cfg.unl.count(std::string(msg_pubkey))) { LOG_DBG << "Peer message pubkey verification failed. Not in UNL."; return -1; } //verify message signature. //this is performed towards end since this is bit expensive std::string_view msg_content = flatbuff_bytes_to_sv(container->content()); if (crypto::verify(msg_content, msg_sig, msg_pubkey) != 0) { LOG_DBG << "Peer message signature verification failed."; return -1; } return 0; } /** * Verifies the Content message structure and outputs faltbuffer Content pointer to access the given buffer. * * @param content_ref A pointer reference to assign the pointer to the Content object. * @param content_ptr Pointer to the the buffer containing the data that should validated and interpreted * via the container pointer. * @param content_size Data buffer size. * @return 0 on successful verification. -1 for failure. */ int validate_and_extract_content(const Content **content_ref, const uint8_t *content_ptr, flatbuffers::uoffset_t content_size) { //Defining Flatbuffer verifier for message content verification. //Since content is also serialised by using Flatbuffer we can verify it using Flatbuffer. flatbuffers::Verifier content_verifier(content_ptr, content_size); //verify content message using flatbuffer verifier. if (!VerifyContainerBuffer(content_verifier)) { LOG_DBG << "Flatbuffer verify: Bad content."; return -1; } *content_ref = GetContent(content_ptr); return 0; } /** * Creates a proposal stuct from the given proposal message. * @param The Flatbuffer poporal received from the peer. * @return A proposal struct representing the message. */ const proposal create_proposal_from_msg(const Proposal_Message &msg, const flatbuffers::Vector *pubkey) { proposal p; p.pubkey = flatbuff_bytes_to_sv(pubkey); p.time = msg.time(); p.stage = msg.stage(); if (msg.lcl()) p.lcl = flatbuff_bytes_to_sv(msg.lcl()); if (msg.users()) p.users = flatbuf_bytearrayvector_to_stringlist(msg.users()); if (msg.raw_inputs()) p.raw_inputs = flatbuf_rawinputs_to_hashbuffermap(msg.raw_inputs()); if (msg.hash_inputs()) p.hash_inputs = flatbuf_bytearrayvector_to_stringlist(msg.hash_inputs()); if (msg.raw_outputs()) p.raw_outputs = flatbuf_rawoutputs_to_hashbuffermap(msg.raw_outputs()); if (msg.hash_outputs()) p.hash_outputs = flatbuf_bytearrayvector_to_stringlist(msg.hash_outputs()); return p; } //---Message creation helpers---// /** * Ctreat proposal peer message from the given proposal struct. * @param container_builder Flatbuffer builder for the container message. * @param p The proposal struct to be placed in the container message. */ void create_msg_from_proposal(flatbuffers::FlatBufferBuilder &container_builder, const proposal &p) { // todo:get a average propsal message size and allocate content builder based on that. flatbuffers::FlatBufferBuilder builder(1024); // Create dummy propsal message flatbuffers::Offset proposal = CreateProposal_Message( builder, p.stage, p.time, sv_to_flatbuff_bytes(builder, p.lcl), stringlist_to_flatbuf_bytearrayvector(builder, p.users), hashbuffermap_to_flatbuf_rawinputs(builder, p.raw_inputs), stringlist_to_flatbuf_bytearrayvector(builder, p.hash_inputs), hashbuffermap_to_flatbuf_rawoutputs(builder, p.raw_outputs), stringlist_to_flatbuf_bytearrayvector(builder, p.hash_outputs)); flatbuffers::Offset message = CreateContent(builder, Message_Proposal_Message, proposal.Union()); builder.Finish(message); // Finished building message content to get serialised content. // Now that we have built the content message, // we need to sign it and place it inside a container message. create_containermsg_from_content(container_builder, builder, true); } /** * Creates a Flatbuffer container message from the given Content message. * @param container_builder The Flatbuffer builder to which the final container message should be written to. * @param content_builder The Flatbuffer builder containing the content message that should be placed * inside the container message. * @param sign Whether to sign the message content. */ void create_containermsg_from_content( flatbuffers::FlatBufferBuilder &container_builder, const flatbuffers::FlatBufferBuilder &content_builder, bool sign) { uint8_t *content_buf = content_builder.GetBufferPointer(); flatbuffers::uoffset_t content_size = content_builder.GetSize(); // Create container message content from serialised content from previous step. flatbuffers::Offset> content = container_builder.CreateVector(content_buf, content_size); flatbuffers::Offset> pubkey_offset = 0; flatbuffers::Offset> sig_offset = 0; if (sign) { // Sign message content with this node's private key. std::string_view content_to_sign(reinterpret_cast(content_buf), content_size); sig_offset = sv_to_flatbuff_bytes(container_builder, crypto::sign(content_to_sign, conf::cfg.seckey)); pubkey_offset = sv_to_flatbuff_bytes(container_builder, conf::cfg.pubkey); } flatbuffers::Offset container_message = CreateContainer( container_builder, util::PEERMSG_VERSION, util::get_epoch_milliseconds(), pubkey_offset, sig_offset, content); // Finish building message container to get serialised message. container_builder.Finish(container_message); } //---Conversion helpers from flatbuffers data types to std data types---// /** * Returns string_view from flat buffer data pointer and length. */ std::string_view flatbuff_bytes_to_sv(const uint8_t *data, flatbuffers::uoffset_t length) { const char *signature_content_str = reinterpret_cast(data); return std::string_view(signature_content_str, length); } /** * Returns return string_view from Flat Buffer vector of bytes. */ std::string_view flatbuff_bytes_to_sv(const flatbuffers::Vector *buffer) { return flatbuff_bytes_to_sv(buffer->Data(), buffer->size()); } /** * Returns set from Flatbuffer vector of ByteArrays. */ const std::unordered_set flatbuf_bytearrayvector_to_stringlist(const flatbuffers::Vector> *fbvec) { std::unordered_set set; set.reserve(fbvec->size()); for (auto el : *fbvec) set.emplace(std::string(flatbuff_bytes_to_sv(el->array()))); return set; } /** * Returns a map from Flatbuffer vector of key value pairs. */ const std::unordered_map flatbuf_pairvector_to_stringmap(const flatbuffers::Vector> *fbvec) { std::unordered_map map; map.reserve(fbvec->size()); for (auto el : *fbvec) map.emplace(flatbuff_bytes_to_sv(el->key()), flatbuff_bytes_to_sv(el->value())); return map; } /** * Returns a hash buffer map from Flatbuffer proposal raw inputs. */ const std::unordered_map> flatbuf_rawinputs_to_hashbuffermap(const flatbuffers::Vector> *fbvec) { std::unordered_map> map; map.reserve(fbvec->size()); for (const RawInputList *user : *fbvec) { std::vector bufvec; bufvec.reserve(user->inputs()->size()); for (auto input : *user->inputs()) { // Create hash_buffer object and manually assign the hash from the input. util::hash_buffer buf(flatbuff_bytes_to_sv(input->value())); //input->value() is the raw input. buf.hash = flatbuff_bytes_to_sv(input->key()); //input->key() is the hash of the input. bufvec.push_back(buf); } map.emplace(flatbuff_bytes_to_sv(user->pubkey()), std::move(bufvec)); } return map; } /** * Returns a hash buffer map from Flatbuffer proposal raw outputs. */ const std::unordered_map flatbuf_rawoutputs_to_hashbuffermap(const flatbuffers::Vector> *fbvec) { std::unordered_map map; map.reserve(fbvec->size()); for (const RawOutput *user : *fbvec) { // Create hash_buffer object and manually assign the hash from the output. util::hash_buffer buf(flatbuff_bytes_to_sv(user->output()->value())); //output->value() is the raw output. buf.hash = flatbuff_bytes_to_sv(user->output()->key()); //output->key() is the hash of the output. map.emplace(flatbuff_bytes_to_sv(user->pubkey()), std::move(buf)); } return map; } //---Conversion helpers from std data types to flatbuffers data types---// //---These are used in constructing Flatbuffer messages using builders---// /** * Returns Flatbuffer bytes vector from string_view. */ const flatbuffers::Offset> sv_to_flatbuff_bytes(flatbuffers::FlatBufferBuilder &builder, std::string_view sv) { return builder.CreateVector(reinterpret_cast(sv.data()), sv.size()); } /** * Returns Flatbuffer vector of ByteArrays from given set of strings. */ const flatbuffers::Offset>> stringlist_to_flatbuf_bytearrayvector(flatbuffers::FlatBufferBuilder &builder, const std::unordered_set &set) { std::vector> fbvec; fbvec.reserve(set.size()); for (std::string_view str : set) fbvec.push_back(CreateByteArray(builder, sv_to_flatbuff_bytes(builder, str))); return builder.CreateVector(fbvec); } /** * Returns Flatbuffer vector of key value pairs from given map. */ const flatbuffers::Offset>> stringmap_to_flatbuf_bytepairvector(flatbuffers::FlatBufferBuilder &builder, const std::unordered_map &map) { std::vector> fbvec; fbvec.reserve(map.size()); for (auto const &[key, value] : map) { fbvec.push_back(CreateBytesKeyValuePair( builder, sv_to_flatbuff_bytes(builder, key), sv_to_flatbuff_bytes(builder, value))); } return builder.CreateVector(fbvec); } /** * Returns Flatbuffer vector of RawInputs from a given map of hash buffer lists. */ const flatbuffers::Offset>> hashbuffermap_to_flatbuf_rawinputs(flatbuffers::FlatBufferBuilder &builder, const std::unordered_map> &map) { std::vector> fbvec; fbvec.reserve(map.size()); for (auto const &[pubkey, bufvec] : map) { std::vector> fbinputsvec; for (const util::hash_buffer &buf : bufvec) { fbinputsvec.push_back(CreateBytesKeyValuePair( builder, sv_to_flatbuff_bytes(builder, buf.hash), sv_to_flatbuff_bytes(builder, buf.buffer))); } fbvec.push_back(CreateRawInputList( builder, sv_to_flatbuff_bytes(builder, pubkey), builder.CreateVector(fbinputsvec))); } return builder.CreateVector(fbvec); } /** * Returns Flatbuffer vector of RawOutputs from a given map of hash buffers. */ const flatbuffers::Offset>> hashbuffermap_to_flatbuf_rawoutputs(flatbuffers::FlatBufferBuilder &builder, const std::unordered_map &map) { std::vector> fbvec; fbvec.reserve(map.size()); for (auto const &[pubkey, buf] : map) { fbvec.push_back(CreateRawOutput( builder, sv_to_flatbuff_bytes(builder, pubkey), CreateBytesKeyValuePair( builder, sv_to_flatbuff_bytes(builder, buf.hash), sv_to_flatbuff_bytes(builder, buf.buffer)))); } return builder.CreateVector(fbvec); } } // namespace p2p