From cd239e1eeafdeb1e6fe2fe95482d806672d3ffee Mon Sep 17 00:00:00 2001 From: study-faraphel Date: Mon, 11 Nov 2024 13:20:23 +0100 Subject: [PATCH] improved packet serialization and readability --- CMakeLists.txt | 9 +++- source/EventManager.cpp | 6 +-- source/events/audio/AudioEvent.cpp | 9 ++-- source/events/audio/AudioEvent.hpp | 2 +- source/events/base/BaseEvent.hpp | 4 +- source/events/info/InfoEvent.cpp | 9 ++-- source/events/info/InfoEvent.hpp | 2 +- source/events/pong/PongEvent.cpp | 2 +- source/events/pong/PongEvent.hpp | 2 +- source/events/search/SearchEvent.cpp | 30 +++++------- source/events/search/SearchEvent.hpp | 2 +- source/packets/audio/AudioPacketData.hpp | 13 +++-- source/packets/base/GenericPacket.hpp | 49 ------------------- .../base/{GenericPacket.cpp => Packet.cpp} | 27 ++-------- source/packets/base/Packet.hpp | 29 +++++++++++ source/packets/base/PacketContent.cpp | 31 ++++++++++++ source/packets/base/PacketContent.hpp | 28 +++++++++++ source/packets/base/PacketData.hpp | 48 ++++++++++++++++++ source/packets/base/SecurityMode.hpp | 14 ++++++ source/packets/info/InfoPacketData.hpp | 21 ++++++++ source/packets/search/SearchPacketData.hpp | 16 ++++-- source/tasks/client/ClientTask.cpp | 17 ++++++- source/tasks/server/ServerTask.cpp | 22 +++++---- source/tasks/undefined/UndefinedTask.cpp | 17 +++---- 24 files changed, 272 insertions(+), 137 deletions(-) delete mode 100644 source/packets/base/GenericPacket.hpp rename source/packets/base/{GenericPacket.cpp => Packet.cpp} (72%) create mode 100644 source/packets/base/Packet.hpp create mode 100644 source/packets/base/PacketContent.cpp create mode 100644 source/packets/base/PacketContent.hpp create mode 100644 source/packets/base/PacketData.hpp create mode 100644 source/packets/base/SecurityMode.hpp create mode 100644 source/packets/info/InfoPacketData.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a524611..0095317 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,9 +25,9 @@ add_executable(M2-PT-DRP source/utils/audio.hpp source/EventManager.cpp source/EventManager.hpp - source/packets/base/GenericPacket.hpp + source/packets/base/Packet.hpp source/events/types.hpp - source/packets/base/GenericPacket.cpp + source/packets/base/Packet.cpp source/RemotePeer.hpp source/events/base/BaseEvent.hpp source/events/base/BaseEvent.hpp @@ -52,6 +52,11 @@ add_executable(M2-PT-DRP source/Context.hpp source/utils/CacheMap.hpp source/packets/search/SearchPacketData.hpp + source/packets/base/PacketContent.cpp + source/packets/base/PacketContent.hpp + source/packets/base/SecurityMode.hpp + source/packets/base/PacketData.hpp + source/packets/info/InfoPacketData.hpp ) target_include_directories(M2-PT-DRP PRIVATE ${MPG123_INCLUDE_DIRS} diff --git a/source/EventManager.cpp b/source/EventManager.cpp index 7eb2c45..6c133e4 100644 --- a/source/EventManager.cpp +++ b/source/EventManager.cpp @@ -16,7 +16,7 @@ #include "events/info/InfoEvent.hpp" #include "events/pong/PongEvent.hpp" #include "events/search/SearchEvent.hpp" -#include "packets/base/GenericPacket.hpp" +#include "packets/base/Packet.hpp" #include "tasks/client/ClientTask.hpp" #include "tasks/server/ServerTask.hpp" #include "tasks/undefined/UndefinedTask.hpp" @@ -156,8 +156,8 @@ void EventManager::loopReceiver() { // prepare space for the sender address sockaddr_storage fromAddress {}; socklen_t fromAddressLength = sizeof(fromAddress); - drp::packet::GenericPacket packet {}; - drp::packet::GenericPacketContent packetContent {}; + drp::packet::base::Packet packet {}; + drp::packet::base::PacketContent packetContent {}; // client loop while (true) { diff --git a/source/events/audio/AudioEvent.cpp b/source/events/audio/AudioEvent.cpp index e81f063..081b8e4 100644 --- a/source/events/audio/AudioEvent.cpp +++ b/source/events/audio/AudioEvent.cpp @@ -1,9 +1,11 @@ #include "AudioEvent.hpp" -#include #include #include +#include "../../packets/audio/AudioPacketData.hpp" +#include "../../packets/base/PacketData.hpp" + namespace drp::event { @@ -31,13 +33,12 @@ AudioEvent::~AudioEvent() { void AudioEvent::handle( Context& context, - const packet::GenericPacketContent& content, + const packet::base::PacketContent& content, const sockaddr_storage& fromAddress, const socklen_t fromAddressLength ) { // get the audio data in the content - packet::AudioPacketData audioData; - std::memcpy(&audioData, content.data.data(), sizeof(packet::AudioPacketData)); + const auto audioData = packet::AudioPacketData::fromGeneric(content); // save it in the audio queue this->audioQueue.push(audioData); diff --git a/source/events/audio/AudioEvent.hpp b/source/events/audio/AudioEvent.hpp index cded46c..6839ac0 100644 --- a/source/events/audio/AudioEvent.hpp +++ b/source/events/audio/AudioEvent.hpp @@ -22,7 +22,7 @@ public: void handle( Context& context, - const packet::GenericPacketContent& content, + const packet::base::PacketContent& content, const sockaddr_storage& fromAddress, socklen_t fromAddressLength ) override; diff --git a/source/events/base/BaseEvent.hpp b/source/events/base/BaseEvent.hpp index 0b71de2..b105e5e 100644 --- a/source/events/base/BaseEvent.hpp +++ b/source/events/base/BaseEvent.hpp @@ -1,6 +1,6 @@ #pragma once -#include "../../packets/base/GenericPacket.hpp" +#include "../../packets/base/Packet.hpp" #include "../../Context.hpp" @@ -12,7 +12,7 @@ public: virtual ~BaseEvent() = default; virtual void handle( Context& context, - const packet::GenericPacketContent& content, + const packet::base::PacketContent& content, const sockaddr_storage& fromAddress, socklen_t fromAddressLength ) = 0; diff --git a/source/events/info/InfoEvent.cpp b/source/events/info/InfoEvent.cpp index f1a117e..57b1bdd 100644 --- a/source/events/info/InfoEvent.cpp +++ b/source/events/info/InfoEvent.cpp @@ -1,24 +1,25 @@ #include "InfoEvent.hpp" -#include #include #include +#include "../../packets/info/InfoPacketData.hpp" + namespace drp::event { void InfoEvent::handle( Context& context, - const packet::GenericPacketContent& content, + const packet::base::PacketContent& content, const sockaddr_storage& fromAddress, const socklen_t fromAddressLength ) { std::cout << "Received peer information." << std::endl; // get the peer information - Peer peer; - std::memcpy(&peer, content.data.data(), sizeof(Peer)); + const auto packetData = packet::info::InfoPacketData::fromGeneric(content); + const Peer peer = packetData.peer; // check if the peer address is already in the map const auto iterator = context.remotePeers.find(peer.id); diff --git a/source/events/info/InfoEvent.hpp b/source/events/info/InfoEvent.hpp index 9ee260d..f3720da 100644 --- a/source/events/info/InfoEvent.hpp +++ b/source/events/info/InfoEvent.hpp @@ -10,7 +10,7 @@ class InfoEvent : public BaseEvent { public: void handle( Context& context, - const packet::GenericPacketContent& content, + const packet::base::PacketContent& content, const sockaddr_storage& fromAddress, socklen_t fromAddressLength ) override; diff --git a/source/events/pong/PongEvent.cpp b/source/events/pong/PongEvent.cpp index 4bfbace..911f2aa 100644 --- a/source/events/pong/PongEvent.cpp +++ b/source/events/pong/PongEvent.cpp @@ -8,7 +8,7 @@ namespace drp::event { void PongEvent::handle( Context& context, - const packet::GenericPacketContent& content, + const packet::base::PacketContent& content, const sockaddr_storage& fromAddress, const socklen_t fromAddressLength ) { diff --git a/source/events/pong/PongEvent.hpp b/source/events/pong/PongEvent.hpp index df720a5..505cab9 100644 --- a/source/events/pong/PongEvent.hpp +++ b/source/events/pong/PongEvent.hpp @@ -10,7 +10,7 @@ class PongEvent : public BaseEvent { public: void handle( Context& context, - const packet::GenericPacketContent& content, + const packet::base::PacketContent& content, const sockaddr_storage& fromAddress, socklen_t fromAddressLength ) override; diff --git a/source/events/search/SearchEvent.cpp b/source/events/search/SearchEvent.cpp index 9006f5d..de36723 100644 --- a/source/events/search/SearchEvent.cpp +++ b/source/events/search/SearchEvent.cpp @@ -6,8 +6,8 @@ #include #include -#include "../../tasks/types.hpp" -#include "../../events/types.hpp" +#include "../../packets/base/SecurityMode.hpp" +#include "../../packets/info/InfoPacketData.hpp" namespace drp { @@ -15,21 +15,21 @@ namespace drp { void event::SearchEvent::handle( Context& context, - const packet::GenericPacketContent& content, + const packet::base::PacketContent& content, const sockaddr_storage& fromAddress, const socklen_t fromAddressLength ) { - packet::GenericPacket packet {}; - packet::GenericPacketContent packetContent {}; + packet::base::Packet packet {}; // create the packet header (available to read for everyone) packet.channel = 0; - packet.securityMode = static_cast(packet::SecurityMode::PLAIN); + packet.securityMode = static_cast(packet::base::SecurityMode::PLAIN); - // insert our information into the packet - packetContent.eventType = static_cast(EventType::INFO); - std::memcpy(&packetContent.data, &context.me, sizeof(context.me)); - packet.setContent(packetContent); + // create the packet data containing our information + packet::info::InfoPacketData packetData {}; + packetData.peer = context.me; + + packet.setContent(packetData.toGeneric()); // TODO(Faraphel): send back the timestamp too @@ -46,15 +46,7 @@ void event::SearchEvent::handle( return; } - char hoststr[NI_MAXHOST]; - char portstr[NI_MAXSERV]; - getnameinfo( - reinterpret_cast(&fromAddress), fromAddressLength, - hoststr, sizeof(hoststr), - portstr, sizeof(portstr), - NI_NUMERICHOST | NI_NUMERICSERV - ); - std::cout << "[Receiver] Sent back information to " << hoststr << ":" << portstr << std::endl; + std::cout << "[Receiver] Sent back information." << std::endl; } diff --git a/source/events/search/SearchEvent.hpp b/source/events/search/SearchEvent.hpp index 1e46c3d..78264f1 100644 --- a/source/events/search/SearchEvent.hpp +++ b/source/events/search/SearchEvent.hpp @@ -9,7 +9,7 @@ class SearchEvent : public BaseEvent { public: void handle( Context& context, - const packet::GenericPacketContent& content, + const packet::base::PacketContent& content, const sockaddr_storage& fromAddress, socklen_t fromAddressLength ) override; diff --git a/source/packets/audio/AudioPacketData.hpp b/source/packets/audio/AudioPacketData.hpp index d2beee9..5c0fcf9 100644 --- a/source/packets/audio/AudioPacketData.hpp +++ b/source/packets/audio/AudioPacketData.hpp @@ -1,14 +1,21 @@ #pragma once -#include #include #include +#include "../../events/types.hpp" +#include "../base/PacketData.hpp" + namespace drp::packet { -struct AudioPacketData { +/** + * Represent the content of an audio packet. + * Contains a chunk of audio and its metadata to play it. + */ +class AudioPacketData : public base::PacketData { +public: // scheduling // TODO(Faraphel): use a more "fixed" size format ? std::chrono::time_point timePlay; @@ -22,4 +29,4 @@ struct AudioPacketData { }; -} \ No newline at end of file +} diff --git a/source/packets/base/GenericPacket.hpp b/source/packets/base/GenericPacket.hpp deleted file mode 100644 index 796b85b..0000000 --- a/source/packets/base/GenericPacket.hpp +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include -#include - - -namespace drp::packet { - - -enum class SecurityMode { - PLAIN = 0x00, - AES = 0x01, - RSA = 0x02, -}; - - -// the maximum data length -// a packet can't be larger than 65565 (uint16 max) -// reserve some space for metadata and settings -constexpr std::uint16_t dataLength = 65504; - -/** - * The content of a generic packet. - * @param eventType the type of event that the packet want to trigger. - * @param data the data of the event. - */ -struct GenericPacketContent { - std::uint8_t eventType; - std::array data; -}; - -/** - * A generic packet that can be transmitted through the network. - * @param channel the channel of the packet. Two system can be created inside a same network by using different - * channels value. "0" is used for "broadcast" message across networks. - * @param securityMode the type of security used in the packet. - * @param _content the content of the packet. It is encrypted accordingly to the securityMode. - */ -struct GenericPacket { - std::uint8_t channel; - std::uint8_t securityMode; - GenericPacketContent _content; - - [[nodiscard]] GenericPacketContent getContent() const; - void setContent(const GenericPacketContent& content); -}; - - -} \ No newline at end of file diff --git a/source/packets/base/GenericPacket.cpp b/source/packets/base/Packet.cpp similarity index 72% rename from source/packets/base/GenericPacket.cpp rename to source/packets/base/Packet.cpp index a20c93e..c80a2ac 100644 --- a/source/packets/base/GenericPacket.cpp +++ b/source/packets/base/Packet.cpp @@ -1,9 +1,11 @@ -#include "GenericPacket.hpp" +#include "Packet.hpp" #include +#include "SecurityMode.hpp" -namespace drp::packet { + +namespace drp::packet::base { /* @@ -47,7 +49,7 @@ GenericPacketContent decryptPacketContentAes(const GenericPacket& packet) { */ -GenericPacketContent GenericPacket::getContent() const { +PacketContent Packet::getContent() const { // TODO(Faraphel): implement RSA and AES // additional "context" argument to hold cryptographic keys ? @@ -68,23 +70,4 @@ GenericPacketContent GenericPacket::getContent() const { } -void GenericPacket::setContent(const GenericPacketContent &content) { - // TODO(Faraphel): implement RSA and AES - switch (static_cast(this->securityMode)) { - case SecurityMode::PLAIN: - this->_content = content; - return; - - case SecurityMode::AES: - throw std::runtime_error("Not implemented."); - - case SecurityMode::RSA: - throw std::runtime_error("Not implemented."); - - default: - throw std::runtime_error("Unsupported security mode."); - } } - - -} \ No newline at end of file diff --git a/source/packets/base/Packet.hpp b/source/packets/base/Packet.hpp new file mode 100644 index 0000000..fa6352c --- /dev/null +++ b/source/packets/base/Packet.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +#include "PacketContent.hpp" + + +namespace drp::packet::base { + + +/** + * A generic packet that can be transmitted through the network. + * @param channel the channel of the packet. Two system can be created inside a same network by using different + * channels value. "0" is used for "broadcast" message across networks. + * @param securityMode the type of security used in the packet. + * @param _content the content of the packet. It is encrypted accordingly to the securityMode. + */ +struct Packet { + std::uint8_t channel; + std::uint8_t securityMode; + PacketContent _content; + + [[nodiscard]] PacketContent getContent() const; + void setContent(const PacketContent& content); +}; + + +} diff --git a/source/packets/base/PacketContent.cpp b/source/packets/base/PacketContent.cpp new file mode 100644 index 0000000..cfa3cdd --- /dev/null +++ b/source/packets/base/PacketContent.cpp @@ -0,0 +1,31 @@ +#include "PacketContent.hpp" + +#include + +#include "Packet.hpp" +#include "SecurityMode.hpp" + + +namespace drp::packet::base { + + +void Packet::setContent(const PacketContent &content) { + // TODO(Faraphel): implement RSA and AES + switch (static_cast(this->securityMode)) { + case SecurityMode::PLAIN: + this->_content = content; + return; + + case SecurityMode::AES: + throw std::runtime_error("Not implemented."); + + case SecurityMode::RSA: + throw std::runtime_error("Not implemented."); + + default: + throw std::runtime_error("Unsupported security mode."); + } +} + + +} diff --git a/source/packets/base/PacketContent.hpp b/source/packets/base/PacketContent.hpp new file mode 100644 index 0000000..cf47ed1 --- /dev/null +++ b/source/packets/base/PacketContent.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + + +namespace drp::packet::base { + + +// the maximum data length +// a packet can't be larger than 65565 (uint16 max) +// reserve some space for metadata and settings +constexpr std::uint16_t dataLength = 65504; + + +/** + * The content of a generic packet. + * @param eventType the type of event that the packet want to trigger. + * @param data the data of the event. + */ +class PacketContent { +public: + std::uint8_t eventType; + std::array data; +}; + + +} diff --git a/source/packets/base/PacketData.hpp b/source/packets/base/PacketData.hpp new file mode 100644 index 0000000..d0c5b77 --- /dev/null +++ b/source/packets/base/PacketData.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include + +#include "PacketContent.hpp" +#include "../../events/types.hpp" + + +namespace drp::packet::base { + + +/** + * Represent the actual data contained inside a packet, with no header. + * Can be used to implement and communicate anything. + * @tparam eventType the event type associated with this type of packet data. + * Allow the receiver to redirect this packet to the correct handler. + */ +template +class PacketData { +public: + /** + * Convert this packet data to a generic packet content. + * @return a generic packet content. + */ + [[nodiscard]] PacketContent toGeneric() const { + // create an empty generic packet content + PacketContent content {}; + // set its content + content.eventType = static_cast(eventType); + std::memcpy(&content.data, this, sizeof(content)); + + return content; + } + + /** + * Get the data from a generic packet data. + * @param content a generic packet content. + * @return the actual packet data. + */ + static packetClass fromGeneric(const PacketContent& content) { + packetClass data; + std::memcpy(&data, content.data.data(), sizeof(packetClass)); + return data; + } +}; + + +} diff --git a/source/packets/base/SecurityMode.hpp b/source/packets/base/SecurityMode.hpp new file mode 100644 index 0000000..3cd3be1 --- /dev/null +++ b/source/packets/base/SecurityMode.hpp @@ -0,0 +1,14 @@ +#pragma once + + +namespace drp::packet::base { + + +enum class SecurityMode { + PLAIN = 0x00, + AES = 0x01, + RSA = 0x02, +}; + + +} \ No newline at end of file diff --git a/source/packets/info/InfoPacketData.hpp b/source/packets/info/InfoPacketData.hpp new file mode 100644 index 0000000..c0bce73 --- /dev/null +++ b/source/packets/info/InfoPacketData.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "../../RemotePeer.hpp" +#include "../../events/types.hpp" +#include "../base/PacketData.hpp" + + +namespace drp::packet::info { + + +/** + * Represent the content of an info packet. + * Contains information about the peer sending it. + */ +class InfoPacketData : public base::PacketData { +public: + Peer peer {}; +}; + + +} diff --git a/source/packets/search/SearchPacketData.hpp b/source/packets/search/SearchPacketData.hpp index 7809895..dcd3297 100644 --- a/source/packets/search/SearchPacketData.hpp +++ b/source/packets/search/SearchPacketData.hpp @@ -1,13 +1,21 @@ #pragma once + #include - -namespace drp::packet { +#include "../base/PacketData.hpp" -typedef struct { +namespace drp::packet::search { + + +/** + * Represent a discovery request. + * Sent by someone to get information about other available machine in the network. + */ +class SearchPacketData : public base::PacketData { +public: std::chrono::time_point timestamp; /// timestamp when the search request was sent -} SearchPacketData; +}; } \ No newline at end of file diff --git a/source/tasks/client/ClientTask.cpp b/source/tasks/client/ClientTask.cpp index 3ee6ef1..efdf2e0 100644 --- a/source/tasks/client/ClientTask.cpp +++ b/source/tasks/client/ClientTask.cpp @@ -1,5 +1,6 @@ #include "ClientTask.hpp" +#include #include @@ -7,7 +8,21 @@ namespace drp::task { void ClientTask::handle(Context& context) { - // TODO(Faraphel): connect to an ntp server + // get the server hostname + char host[NI_MAXHOST]; + char port[NI_MAXSERV]; + getnameinfo( + reinterpret_cast(&context.server->address), context.server->addressLength, + host, sizeof(host), + port, sizeof(port), + NI_NUMERICHOST | NI_NUMERICSERV + ); + + // connect to the chrony server + // TODO(Faraphel): only once ? + FILE* chronyProcess = popen(("chronyc add server " + std::string(host) + " 2>&1").c_str(), "r"); + if (pclose(chronyProcess) == -1) + std::cerr << "Failed to connect to chrony server !" << std::endl; // TODO(Faraphel): check if the server is still reachable. // if connection lost, go back to undefined mode. diff --git a/source/tasks/server/ServerTask.cpp b/source/tasks/server/ServerTask.cpp index 0e07ff3..c8b1617 100644 --- a/source/tasks/server/ServerTask.cpp +++ b/source/tasks/server/ServerTask.cpp @@ -10,9 +10,9 @@ #include #include -#include "../../events/types.hpp" #include "../../packets/audio/AudioPacketData.hpp" -#include "../../packets/base/GenericPacket.hpp" +#include "../../packets/base/Packet.hpp" +#include "../../packets/base/SecurityMode.hpp" #include "../../utils/audio.hpp" @@ -57,17 +57,22 @@ ServerTask::~ServerTask() { void ServerTask::handle(Context& context) { // TODO(Faraphel): create a chrony server - // read the file - packet::GenericPacket packet {}; - packet::GenericPacketContent packetContent {}; + // allow anybody to connect as a chrony client + FILE* chronyProcess = popen("chronyc allow all 2>&1", "r"); + if (pclose(chronyProcess) == -1) + std::cerr << "Could not allow chrony connection." << std::endl; + + // prepare the packet structure + packet::base::Packet packet {}; packet::AudioPacketData audioPacket; std::size_t done; // create a packet // TODO(Faraphel): should not be broadcast plaintext packet.channel = 0; - packet.securityMode = static_cast(packet::SecurityMode::PLAIN); + packet.securityMode = static_cast(packet::base::SecurityMode::PLAIN); + // read the file if (mpg123_read( this->mpgHandle, &audioPacket.content, @@ -93,10 +98,7 @@ void ServerTask::handle(Context& context) { audioPacket.sampleFormat = util::encoding_mpg123_to_PulseAudio(this->encoding); audioPacket.sampleRate = this->sampleRate; - // wrap the audio packet in the content - packetContent.eventType = static_cast(event::EventType::AUDIO); - std::memcpy(packetContent.data.data(), &audioPacket, packetContent.data.size()); - packet.setContent(packetContent); + packet.setContent(audioPacket.toGeneric()); // broadcast the audio data if (sendto( diff --git a/source/tasks/undefined/UndefinedTask.cpp b/source/tasks/undefined/UndefinedTask.cpp index dd4df41..b155c0d 100644 --- a/source/tasks/undefined/UndefinedTask.cpp +++ b/source/tasks/undefined/UndefinedTask.cpp @@ -10,8 +10,8 @@ #include #include "../../Context.hpp" -#include "../../events/types.hpp" -#include "../../packets/base/GenericPacket.hpp" +#include "../../packets/base/Packet.hpp" +#include "../../packets/base/SecurityMode.hpp" #include "../../packets/search/SearchPacketData.hpp" @@ -75,18 +75,17 @@ void UndefinedTask::handle(Context& context) { } // prepare a search message - packet::GenericPacket packet {}; - packet::GenericPacketContent packetContent {}; - packet::SearchPacketData packetData {}; + packet::base::Packet packet {}; + packet::search::SearchPacketData packetData {}; // broadcast message packet.channel = 0; - packet.securityMode = static_cast(packet::SecurityMode::PLAIN); + packet.securityMode = static_cast(packet::base::SecurityMode::PLAIN); + // search message with the time of the message being sent - packetContent.eventType = static_cast(event::EventType::SEARCH); packetData.timestamp = std::chrono::high_resolution_clock::now(); - std::memcpy(&packetContent.data, &packetData, sizeof(packetData)); - packet.setContent(packetContent); + + packet.setContent(packetData.toGeneric()); std::cout << "Looking for new peers." << std::endl;