diff --git a/nano/core_test/telemetry.cpp b/nano/core_test/telemetry.cpp index 2a1fac46..add7d32d 100644 --- a/nano/core_test/telemetry.cpp +++ b/nano/core_test/telemetry.cpp @@ -193,7 +193,7 @@ TEST (telemetry, signatures) data.maker = 1; data.timestamp = std::chrono::system_clock::time_point (100ms); data.sign (node_id); - ASSERT_FALSE (data.validate_signature (nano::telemetry_data::size)); + ASSERT_FALSE (data.validate_signature ()); auto signature = data.signature; // Check that the signature is different if changing a piece of data data.maker = 2; @@ -201,6 +201,22 @@ TEST (telemetry, signatures) ASSERT_NE (data.signature, signature); } +TEST (telemetry, unknown_data) +{ + nano::keypair node_id; + nano::telemetry_data data; + data.node_id = node_id.pub; + data.major_version = 20; + data.minor_version = 1; + data.patch_version = 5; + data.pre_release_version = 2; + data.maker = 1; + data.timestamp = std::chrono::system_clock::time_point (100ms); + data.unknown_data.push_back (1); + data.sign (node_id); + ASSERT_FALSE (data.validate_signature ()); +} + TEST (telemetry, no_peers) { nano::system system (1); @@ -504,6 +520,29 @@ TEST (telemetry, disable_metrics) ASSERT_TIMELY (10s, done); } +TEST (telemetry, max_possible_size) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.disable_initial_telemetry_requests = true; + node_flags.disable_ongoing_telemetry_requests = true; + auto node_client = system.add_node (node_flags); + auto node_server = system.add_node (node_flags); + + nano::telemetry_data data; + data.unknown_data.resize (nano::message_header::telemetry_size_mask.to_ulong () - nano::telemetry_data::latest_size); + + nano::telemetry_ack message (data); + wait_peer_connections (system); + + auto channel = node_client->network.tcp_channels.find_channel (nano::transport::map_endpoint_to_tcp (node_server->network.endpoint ())); + channel->send (message, [](boost::system::error_code const & ec, size_t size_a) { + ASSERT_FALSE (ec); + }); + + ASSERT_TIMELY (10s, 1 == node_server->stats.count (nano::stat::type::message, nano::stat::detail::telemetry_ack, nano::stat::dir::in)); +} + namespace nano { TEST (telemetry, remove_peer_different_genesis) @@ -634,4 +673,4 @@ TEST (telemetry, maker_pruning) } ASSERT_EQ (nano::telemetry_maker::nf_pruned_node, static_cast (telemetry_data.maker)); -} \ No newline at end of file +} diff --git a/nano/lib/stream.hpp b/nano/lib/stream.hpp index c5c537ae..8fe0be5b 100644 --- a/nano/lib/stream.hpp +++ b/nano/lib/stream.hpp @@ -10,11 +10,11 @@ namespace nano using stream = std::basic_streambuf; // Read a raw byte stream the size of `T' and fill value. Returns true if there was an error, false otherwise template -bool try_read (nano::stream & stream_a, T & value) +bool try_read (nano::stream & stream_a, T & value_a) { static_assert (std::is_standard_layout::value, "Can't stream read non-standard layout types"); - auto amount_read (stream_a.sgetn (reinterpret_cast (&value), sizeof (value))); - return amount_read != sizeof (value); + auto amount_read (stream_a.sgetn (reinterpret_cast (&value_a), sizeof (value_a))); + return amount_read != sizeof (value_a); } // A wrapper of try_read which throws if there is an error template @@ -27,12 +27,28 @@ void read (nano::stream & stream_a, T & value) } } +inline void read (nano::stream & stream_a, std::vector & value_a, size_t size_a) +{ + value_a.resize (size_a); + if (stream_a.sgetn (value_a.data (), size_a) != size_a) + { + throw std::runtime_error ("Failed to read this number of bytes"); + } +} + template -void write (nano::stream & stream_a, T const & value) +void write (nano::stream & stream_a, T const & value_a) { static_assert (std::is_standard_layout::value, "Can't stream write non-standard layout types"); - auto amount_written (stream_a.sputn (reinterpret_cast (&value), sizeof (value))); + auto amount_written (stream_a.sputn (reinterpret_cast (&value_a), sizeof (value_a))); (void)amount_written; - debug_assert (amount_written == sizeof (value)); + debug_assert (amount_written == sizeof (value_a)); +} + +inline void write (nano::stream & stream_a, std::vector const & value_a) +{ + auto amount_written (stream_a.sputn (value_a.data (), value_a.size ())); + (void)amount_written; + debug_assert (amount_written == value_a.size ()); } } diff --git a/nano/node/common.cpp b/nano/node/common.cpp index 70113fb1..5b2c06a5 100644 --- a/nano/node/common.cpp +++ b/nano/node/common.cpp @@ -1108,9 +1108,9 @@ nano::telemetry_ack::telemetry_ack (nano::telemetry_data const & telemetry_data_ message (nano::message_type::telemetry_ack), data (telemetry_data_a) { - debug_assert (telemetry_data::size < 2048); // Maximum size the mask allows + debug_assert (telemetry_data::size + telemetry_data_a.unknown_data.size () <= message_header::telemetry_size_mask.to_ulong ()); // Maximum size the mask allows header.extensions &= ~message_header::telemetry_size_mask; - header.extensions |= std::bitset<16> (static_cast (telemetry_data::size)); + header.extensions |= std::bitset<16> (static_cast (telemetry_data::size) + telemetry_data_a.unknown_data.size ()); } void nano::telemetry_ack::serialize (nano::stream & stream_a, bool use_epoch_2_min_version_a) const @@ -1193,9 +1193,13 @@ void nano::telemetry_data::deserialize (nano::stream & stream_a, uint16_t payloa timestamp = std::chrono::system_clock::time_point (std::chrono::milliseconds (timestamp_l)); read (stream_a, active_difficulty); boost::endian::big_to_native_inplace (active_difficulty); + if (payload_length_a > latest_size) + { + read (stream_a, unknown_data, payload_length_a - latest_size); + } } -void nano::telemetry_data::serialize_without_signature (nano::stream & stream_a, uint16_t /* size_a */) const +void nano::telemetry_data::serialize_without_signature (nano::stream & stream_a) const { // All values should be serialized in big endian write (stream_a, node_id); @@ -1215,12 +1219,13 @@ void nano::telemetry_data::serialize_without_signature (nano::stream & stream_a, write (stream_a, maker); write (stream_a, boost::endian::native_to_big (std::chrono::duration_cast (timestamp.time_since_epoch ()).count ())); write (stream_a, boost::endian::native_to_big (active_difficulty)); + write (stream_a, unknown_data); } void nano::telemetry_data::serialize (nano::stream & stream_a) const { write (stream_a, signature); - serialize_without_signature (stream_a, size); + serialize_without_signature (stream_a); } nano::error nano::telemetry_data::serialize_json (nano::jsonconfig & json, bool ignore_identification_metrics_a) const @@ -1307,7 +1312,7 @@ nano::error nano::telemetry_data::deserialize_json (nano::jsonconfig & json, boo bool nano::telemetry_data::operator== (nano::telemetry_data const & data_a) const { - return (signature == data_a.signature && node_id == data_a.node_id && block_count == data_a.block_count && cemented_count == data_a.cemented_count && unchecked_count == data_a.unchecked_count && account_count == data_a.account_count && bandwidth_cap == data_a.bandwidth_cap && uptime == data_a.uptime && peer_count == data_a.peer_count && protocol_version == data_a.protocol_version && genesis_block == data_a.genesis_block && major_version == data_a.major_version && minor_version == data_a.minor_version && patch_version == data_a.patch_version && pre_release_version == data_a.pre_release_version && maker == data_a.maker && timestamp == data_a.timestamp && active_difficulty == data_a.active_difficulty); + return (signature == data_a.signature && node_id == data_a.node_id && block_count == data_a.block_count && cemented_count == data_a.cemented_count && unchecked_count == data_a.unchecked_count && account_count == data_a.account_count && bandwidth_cap == data_a.bandwidth_cap && uptime == data_a.uptime && peer_count == data_a.peer_count && protocol_version == data_a.protocol_version && genesis_block == data_a.genesis_block && major_version == data_a.major_version && minor_version == data_a.minor_version && patch_version == data_a.patch_version && pre_release_version == data_a.pre_release_version && maker == data_a.maker && timestamp == data_a.timestamp && active_difficulty == data_a.active_difficulty && unknown_data == data_a.unknown_data); } bool nano::telemetry_data::operator!= (nano::telemetry_data const & data_a) const @@ -1321,18 +1326,18 @@ void nano::telemetry_data::sign (nano::keypair const & node_id_a) std::vector bytes; { nano::vectorstream stream (bytes); - serialize_without_signature (stream, size); + serialize_without_signature (stream); } signature = nano::sign_message (node_id_a.prv, node_id_a.pub, bytes.data (), bytes.size ()); } -bool nano::telemetry_data::validate_signature (uint16_t size_a) const +bool nano::telemetry_data::validate_signature () const { std::vector bytes; { nano::vectorstream stream (bytes); - serialize_without_signature (stream, size_a); + serialize_without_signature (stream); } return nano::validate_message (node_id, bytes.data (), bytes.size (), signature); diff --git a/nano/node/common.hpp b/nano/node/common.hpp index 2d9b21cb..2d820d27 100644 --- a/nano/node/common.hpp +++ b/nano/node/common.hpp @@ -220,7 +220,7 @@ public: static std::bitset<16> constexpr block_type_mask{ 0x0f00 }; static std::bitset<16> constexpr count_mask{ 0xf000 }; - static std::bitset<16> constexpr telemetry_size_mask{ 0x07ff }; + static std::bitset<16> constexpr telemetry_size_mask{ 0x3ff }; }; class message { @@ -368,20 +368,22 @@ public: uint8_t maker{ static_cast> (telemetry_maker::nf_node) }; // Where this telemetry information originated std::chrono::system_clock::time_point timestamp; uint64_t active_difficulty{ 0 }; + std::vector unknown_data; void serialize (nano::stream &) const; void deserialize (nano::stream &, uint16_t); nano::error serialize_json (nano::jsonconfig &, bool) const; nano::error deserialize_json (nano::jsonconfig &, bool); void sign (nano::keypair const &); - bool validate_signature (uint16_t) const; + bool validate_signature () const; bool operator== (nano::telemetry_data const &) const; bool operator!= (nano::telemetry_data const &) const; + // Size does not include unknown_data static auto constexpr size = sizeof (signature) + sizeof (node_id) + sizeof (block_count) + sizeof (cemented_count) + sizeof (unchecked_count) + sizeof (account_count) + sizeof (bandwidth_cap) + sizeof (peer_count) + sizeof (protocol_version) + sizeof (uptime) + sizeof (genesis_block) + sizeof (major_version) + sizeof (minor_version) + sizeof (patch_version) + sizeof (pre_release_version) + sizeof (maker) + sizeof (uint64_t) + sizeof (active_difficulty); - + static auto constexpr latest_size = size; // This needs to be updated for each new telemetry version private: - void serialize_without_signature (nano::stream &, uint16_t) const; + void serialize_without_signature (nano::stream &) const; }; class telemetry_req final : public message { diff --git a/nano/node/telemetry.cpp b/nano/node/telemetry.cpp index ec8bd0bf..6caf73a7 100644 --- a/nano/node/telemetry.cpp +++ b/nano/node/telemetry.cpp @@ -90,7 +90,7 @@ bool nano::telemetry::verify_message (nano::telemetry_ack const & message_a, nan if (!node_id_mismatch) { // The data could be correctly signed but for a different node id - remove_channel = message_a.data.validate_signature (message_a.size ()); + remove_channel = message_a.data.validate_signature (); if (!remove_channel) { // Check for different genesis blocks diff --git a/nano/test_common/telemetry.cpp b/nano/test_common/telemetry.cpp index 39d1d2f6..923bdc7b 100644 --- a/nano/test_common/telemetry.cpp +++ b/nano/test_common/telemetry.cpp @@ -21,11 +21,12 @@ void nano::compare_default_telemetry_response_data_excluding_signature (nano::te ASSERT_EQ (telemetry_data_a.maker, static_cast> (nano::telemetry_maker::nf_node)); ASSERT_GT (telemetry_data_a.timestamp, std::chrono::system_clock::now () - std::chrono::seconds (100)); ASSERT_EQ (telemetry_data_a.active_difficulty, active_difficulty_a); + ASSERT_EQ (telemetry_data_a.unknown_data, std::vector{}); } void nano::compare_default_telemetry_response_data (nano::telemetry_data const & telemetry_data_a, nano::network_params const & network_params_a, uint64_t bandwidth_limit_a, uint64_t active_difficulty_a, nano::keypair const & node_id_a) { - ASSERT_FALSE (telemetry_data_a.validate_signature (nano::telemetry_data::size)); + ASSERT_FALSE (telemetry_data_a.validate_signature ()); nano::telemetry_data telemetry_data_l = telemetry_data_a; telemetry_data_l.signature.clear (); telemetry_data_l.sign (node_id_a);