fix: crash in peer stats (#5279)

This commit is contained in:
Charles Kerr 2023-04-14 16:03:08 -05:00 committed by GitHub
parent 6156d90917
commit d445c7f061
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 201 additions and 178 deletions

View File

@ -274,8 +274,6 @@ struct tr_swarm_stats
tr_swarm_stats tr_swarmGetStats(tr_swarm const* swarm);
void tr_swarmIncrementActivePeers(tr_swarm* swarm, tr_direction direction, bool is_active);
// ---
#ifdef _WIN32

View File

@ -369,7 +369,7 @@ public:
[&now](auto const& webseed) { return webseed->isTransferringPieces(now, TR_DOWN, nullptr); });
}
[[nodiscard]] auto peerCount() const noexcept
[[nodiscard]] TR_CONSTEXPR20 auto peerCount() const noexcept
{
return std::size(peers);
}
@ -977,7 +977,7 @@ namespace
{
namespace handshake_helpers
{
void create_bit_torrent_peer(tr_torrent* tor, std::shared_ptr<tr_peerIo> io, struct peer_atom* atom, tr_quark client)
void create_bit_torrent_peer(tr_torrent* tor, std::shared_ptr<tr_peerIo> io, struct peer_atom* atom, tr_interned_string client)
{
TR_ASSERT(atom != nullptr);
TR_ASSERT(tr_isTorrent(tor));
@ -985,8 +985,7 @@ void create_bit_torrent_peer(tr_torrent* tor, std::shared_ptr<tr_peerIo> io, str
tr_swarm* swarm = tor->swarm;
auto* peer = tr_peerMsgsNew(tor, atom, std::move(io), &tr_swarm::peerCallbackFunc, swarm);
peer->client = client;
auto* peer = tr_peerMsgsNew(tor, atom, std::move(io), client, &tr_swarm::peerCallbackFunc, swarm);
atom->is_connected = true;
swarm->peers.push_back(peer);
@ -996,11 +995,6 @@ void create_bit_torrent_peer(tr_torrent* tor, std::shared_ptr<tr_peerIo> io, str
TR_ASSERT(swarm->stats.peer_count == swarm->peerCount());
TR_ASSERT(swarm->stats.peer_from_count[atom->fromFirst] <= swarm->stats.peer_count);
// TODO is this needed?
// isn't it already initialized in tr_peerMsgsImpl's ctor?
peer->update_active(TR_UP);
peer->update_active(TR_DOWN);
}
/* FIXME: this is kind of a mess. */
@ -1084,12 +1078,12 @@ void create_bit_torrent_peer(tr_torrent* tor, std::shared_ptr<tr_peerIo> io, str
}
else
{
auto client = tr_quark{ TR_KEY_NONE };
auto client = tr_interned_string{};
if (result.peer_id)
{
auto buf = std::array<char, 128>{};
tr_clientForId(std::data(buf), sizeof(buf), *result.peer_id);
client = tr_quark_new(std::data(buf));
client = tr_interned_string{ tr_quark_new(std::data(buf)) };
}
result.io->set_bandwidth(&s->tor->bandwidth_);
@ -1324,10 +1318,11 @@ std::vector<tr_pex> tr_peerMgrGetPeers(tr_torrent const* tor, uint8_t address_ty
auto atoms = std::vector<peer_atom const*>{};
if (list_mode == TR_PEERS_CONNECTED) /* connected peers only */
{
atoms.reserve(s->peerCount());
auto const& peers = s->peers;
atoms.reserve(std::size(peers));
std::transform(
std::begin(s->peers),
std::end(s->peers),
std::begin(peers),
std::end(peers),
std::back_inserter(atoms),
[](auto const* peer) { return peer->atom; });
}
@ -1421,13 +1416,6 @@ void tr_peerMgrOnTorrentGotMetainfo(tr_torrent* tor)
swarm->mark_atom_as_seed(*peer->atom);
}
}
/* update the bittorrent peers' willingness... */
for (auto* peer : swarm->peers)
{
peer->update_active(TR_UP);
peer->update_active(TR_DOWN);
}
}
int8_t tr_peerMgrPieceAvailability(tr_torrent const* tor, tr_piece_index_t piece)
@ -1462,33 +1450,25 @@ void tr_peerMgrTorrentAvailability(tr_torrent const* tor, int8_t* tab, unsigned
}
}
tr_swarm_stats tr_swarmGetStats(tr_swarm const* swarm)
tr_swarm_stats tr_swarmGetStats(tr_swarm const* const swarm)
{
TR_ASSERT(swarm != nullptr);
auto count_active_peers = [&swarm](tr_direction dir)
{
return std::count_if(
std::begin(swarm->peers),
std::end(swarm->peers),
[dir](auto const& peer) { return peer->is_active(dir); });
};
auto& stats = swarm->stats;
stats.active_peer_count[TR_UP] = count_active_peers(TR_UP);
stats.active_peer_count[TR_DOWN] = count_active_peers(TR_DOWN);
stats.active_webseed_count = swarm->countActiveWebseeds(tr_time_msec());
return stats;
}
void tr_swarmIncrementActivePeers(tr_swarm* swarm, tr_direction direction, bool is_active)
{
int n = swarm->stats.active_peer_count[direction];
if (is_active)
{
++n;
}
else
{
--n;
}
TR_ASSERT(n >= 0);
TR_ASSERT(n <= swarm->stats.peer_count);
swarm->stats.active_peer_count[direction] = n;
}
/* count how many bytes we want that connected peers have */
uint64_t tr_peerMgrGetDesiredAvailable(tr_torrent const* tor)
{
@ -1502,7 +1482,7 @@ uint64_t tr_peerMgrGetDesiredAvailable(tr_torrent const* tor)
}
tr_swarm const* const swarm = tor->swarm;
if (swarm == nullptr || swarm->peerCount() == 0U)
if (swarm == nullptr || std::empty(swarm->peers))
{
return 0;
}
@ -1547,7 +1527,7 @@ namespace
namespace peer_stat_helpers
{
[[nodiscard]] auto getPeerStats(tr_peerMsgs const* peer, time_t now, uint64_t now_msec)
[[nodiscard]] auto get_peer_stats(tr_peerMsgs const* peer, time_t now, uint64_t now_msec)
{
auto stats = tr_peer_stat{};
auto const* const atom = peer->atom;
@ -1555,7 +1535,7 @@ namespace peer_stat_helpers
auto const [addr, port] = peer->socketAddress();
addr.display_name(stats.addr, sizeof(stats.addr));
stats.client = peer->client.c_str();
stats.client = peer->user_agent().c_str();
stats.port = port.host();
stats.from = atom->fromFirst;
stats.progress = peer->percentDone();
@ -1563,10 +1543,10 @@ namespace peer_stat_helpers
stats.isEncrypted = peer->is_encrypted();
stats.rateToPeer_KBps = tr_toSpeedKBps(peer->get_piece_speed_bytes_per_second(now_msec, TR_CLIENT_TO_PEER));
stats.rateToClient_KBps = tr_toSpeedKBps(peer->get_piece_speed_bytes_per_second(now_msec, TR_PEER_TO_CLIENT));
stats.peerIsChoked = peer->is_peer_choked();
stats.peerIsInterested = peer->is_peer_interested();
stats.clientIsChoked = peer->is_client_choked();
stats.clientIsInterested = peer->is_client_interested();
stats.peerIsChoked = peer->peer_is_choked();
stats.peerIsInterested = peer->peer_is_interested();
stats.clientIsChoked = peer->client_is_choked();
stats.clientIsInterested = peer->client_is_interested();
stats.isIncoming = peer->is_incoming_connection();
stats.isDownloadingFrom = peer->is_active(TR_PEER_TO_CLIENT);
stats.isUploadingTo = peer->is_active(TR_CLIENT_TO_PEER);
@ -1654,16 +1634,17 @@ tr_peer_stat* tr_peerMgrPeerStats(tr_torrent const* tor, size_t* setme_count)
TR_ASSERT(tr_isTorrent(tor));
TR_ASSERT(tor->swarm->manager != nullptr);
auto const n = tor->swarm->peerCount();
auto const peers = tor->swarm->peers;
auto const n = std::size(peers);
auto* const ret = new tr_peer_stat[n];
auto const now = tr_time();
auto const now_msec = tr_time_msec();
std::transform(
std::begin(tor->swarm->peers),
std::end(tor->swarm->peers),
std::begin(peers),
std::end(peers),
ret,
[&now, &now_msec](auto const* peer) { return getPeerStats(peer, now, now_msec); });
[&now, &now_msec](auto const* peer) { return get_peer_stats(peer, now, now_msec); });
*setme_count = n;
return ret;
@ -1718,7 +1699,7 @@ void updateInterest(tr_swarm* swarm)
return;
}
if (auto const peer_count = swarm->peerCount(); peer_count > 0)
if (auto const& peers = swarm->peers; !std::empty(peers))
{
int const n = tor->pieceCount();
@ -1730,7 +1711,7 @@ void updateInterest(tr_swarm* swarm)
piece_is_interesting[i] = tor->pieceIsWanted(i) && !tor->hasPiece(i);
}
for (auto* const peer : swarm->peers)
for (auto* const peer : peers)
{
peer->set_interested(isPeerInteresting(tor, piece_is_interesting, peer));
}
@ -1818,8 +1799,8 @@ void rechokeUploads(tr_swarm* s, uint64_t const now)
{
auto const lock = s->unique_lock();
auto const peer_count = s->peerCount();
auto& peers = s->peers;
auto const peer_count = std::size(peers);
auto choked = std::vector<ChokeData>{};
choked.reserve(peer_count);
auto const* const session = s->manager->session;
@ -1857,8 +1838,8 @@ void rechokeUploads(tr_swarm* s, uint64_t const now)
peer,
getRateBps(s->tor, peer, now),
salter(),
peer->is_peer_interested(),
peer->is_peer_choked(),
peer->peer_is_interested(),
peer->peer_is_choked(),
true);
}
}
@ -2071,9 +2052,11 @@ struct ComparePeerByActivity
[[nodiscard]] auto getPeersToClose(tr_swarm const* const swarm, time_t const now_sec)
{
auto peers_to_close = std::vector<tr_peer*>{};
auto const& peers = swarm->peers;
auto const peer_count = std::size(peers);
auto const peer_count = swarm->peerCount();
auto peers_to_close = std::vector<tr_peer*>{};
peers_to_close.reserve(peer_count);
for (auto* peer : swarm->peers)
{
if (shouldPeerBeClosed(swarm, peer, peer_count, now_sec))

View File

@ -261,7 +261,7 @@ void updateDesiredRequestCount(tr_peerMsgsImpl* msgs);
__FILE__, \
__LINE__, \
(level), \
fmt::format(FMT_STRING("{:s} [{:s}]: {:s}"), (msgs)->io->display_name(), (msgs)->client, text), \
fmt::format(FMT_STRING("{:s} [{:s}]: {:s}"), (msgs)->io->display_name(), (msgs)->user_agent().sv(), text), \
(msgs)->torrent->name()); \
} \
} while (0)
@ -270,6 +270,8 @@ void updateDesiredRequestCount(tr_peerMsgsImpl* msgs);
#define logtrace(msgs, text) myLogMacro(msgs, TR_LOG_TRACE, text)
#define logwarn(msgs, text) myLogMacro(msgs, TR_LOG_WARN, text)
using ReadResult = std::pair<ReadState, size_t /*n_piece_data_bytes_read*/>;
/**
* Low-level communication state information about a connected peer.
*
@ -291,9 +293,10 @@ public:
tr_torrent* torrent_in,
peer_atom* atom_in,
std::shared_ptr<tr_peerIo> io_in,
tr_interned_string client,
tr_peer_callback callback,
void* callback_data)
: tr_peerMsgs{ torrent_in, atom_in }
: tr_peerMsgs{ torrent_in, atom_in, client, io_in->is_encrypted(), io_in->is_incoming(), io_in->is_utp() }
, outMessagesBatchPeriod{ LowPriorityIntervalSecs }
, torrent{ torrent_in }
, io{ std::move(io_in) }
@ -331,6 +334,8 @@ public:
io->set_callbacks(canRead, didWrite, gotError, this);
updateDesiredRequestCount(this);
update_active();
}
tr_peerMsgsImpl(tr_peerMsgsImpl&&) = delete;
@ -391,61 +396,11 @@ public:
}
}
[[nodiscard]] bool is_peer_choked() const noexcept override
{
return peer_is_choked_;
}
[[nodiscard]] bool is_peer_interested() const noexcept override
{
return peer_is_interested_;
}
[[nodiscard]] bool is_client_choked() const noexcept override
{
return client_is_choked_;
}
[[nodiscard]] bool is_client_interested() const noexcept override
{
return client_is_interested_;
}
[[nodiscard]] bool is_utp_connection() const noexcept override
{
return io->is_utp();
}
[[nodiscard]] bool is_encrypted() const override
{
return io->is_encrypted();
}
[[nodiscard]] bool is_incoming_connection() const override
{
return io->is_incoming();
}
[[nodiscard]] tr_bandwidth& bandwidth() noexcept override
{
return io->bandwidth();
}
[[nodiscard]] bool is_active(tr_direction direction) const override
{
TR_ASSERT(tr_isDirection(direction));
auto const active = is_active_[direction];
TR_ASSERT(active == calculate_active(direction));
return active;
}
void update_active(tr_direction direction) override
{
TR_ASSERT(tr_isDirection(direction));
set_active(direction, calculate_active(direction));
}
[[nodiscard]] std::pair<tr_address, tr_port> socketAddress() const override
{
return io->socket_address();
@ -465,6 +420,8 @@ public:
void onTorrentGotMetainfo() noexcept override
{
invalidatePercentDone();
update_active();
}
void invalidatePercentDone()
@ -486,16 +443,16 @@ public:
{
// TODO logtrace(msgs, "Not changing choke to %d to avoid fibrillation", peer_is_choked);
}
else if (peer_is_choked_ != peer_is_choked)
else if (this->peer_is_choked() != peer_is_choked)
{
peer_is_choked_ = peer_is_choked;
set_peer_choked(peer_is_choked);
if (peer_is_choked_)
if (peer_is_choked)
{
cancelAllRequestsToClient(this);
}
protocolSendChoke(this, peer_is_choked_);
protocolSendChoke(this, peer_is_choked);
chokeChangedAt = now;
update_active(TR_CLIENT_TO_PEER);
}
@ -516,9 +473,9 @@ public:
void set_interested(bool interested) override
{
if (client_is_interested_ != interested)
if (client_is_interested() != interested)
{
client_is_interested_ = interested;
set_client_interested(interested);
sendInterest(this, interested);
update_active(TR_PEER_TO_CLIENT);
}
@ -539,8 +496,8 @@ public:
void requestBlocks(tr_block_span_t const* block_spans, size_t n_spans) override
{
TR_ASSERT(torrent->clientCanDownload());
TR_ASSERT(is_client_interested());
TR_ASSERT(!is_client_choked());
TR_ASSERT(client_is_interested());
TR_ASSERT(!client_is_choked());
for (auto const *span = block_spans, *span_end = span + n_spans; span != span_end; ++span)
{
@ -587,7 +544,7 @@ public:
private:
[[nodiscard]] size_t maxAvailableReqs() const
{
if (torrent->isDone() || !torrent->hasMetainfo() || client_is_choked_ || !client_is_interested_)
if (torrent->isDone() || !torrent->hasMetainfo() || client_is_choked() || !client_is_interested())
{
return 0;
}
@ -644,7 +601,7 @@ private:
{
if (direction == TR_CLIENT_TO_PEER)
{
return is_peer_interested() && !is_peer_choked();
return peer_is_interested() && !peer_is_choked();
}
// TR_PEER_TO_CLIENT
@ -654,36 +611,25 @@ private:
return true;
}
auto const active = is_client_interested() && !is_client_choked();
auto const active = client_is_interested() && !client_is_choked();
TR_ASSERT(!active || !torrent->isDone());
return active;
}
void set_active(tr_direction direction, bool active)
void update_active()
{
// TODO logtrace(msgs, "direction [%d] is_active [%d]", int(direction), int(is_active));
auto& val = is_active_[direction];
if (val != active)
{
val = active;
update_active(TR_UP);
update_active(TR_DOWN);
}
tr_swarmIncrementActivePeers(torrent->swarm, direction, active);
}
void update_active(tr_direction direction)
{
TR_ASSERT(tr_isDirection(direction));
set_active(direction, calculate_active(direction));
}
public:
/* Whether or not we've choked this peer. */
bool peer_is_choked_ = true;
/* whether or not the peer has indicated it will download from us. */
bool peer_is_interested_ = false;
/* whether or not the peer is choking us. */
bool client_is_choked_ = true;
/* whether or not we've indicated to the peer that we would download from them if unchoked. */
bool client_is_interested_ = false;
bool peerSupportsPex = false;
bool peerSupportsMetadataXfer = false;
bool clientSentLtepHandshake = false;
@ -746,7 +692,8 @@ public:
tr_bitfield have_;
private:
std::array<bool, 2> is_active_ = { false, false };
friend ReadResult process_peer_message(tr_peerMsgsImpl* msgs, uint8_t id, libtransmission::Buffer& payload);
friend void parseLtepHandshake(tr_peerMsgsImpl* msgs, libtransmission::Buffer& payload);
tr_peer_callback const callback_;
void* const callback_data_;
@ -1086,6 +1033,15 @@ void parseLtepHandshake(tr_peerMsgsImpl* msgs, libtransmission::Buffer& payload)
pex.flags |= ADDED_F_SEED_FLAG;
}
// http://bittorrent.org/beps/bep_0010.html
// Client name and version (as a utf-8 string). This is a much more
// reliable way of identifying the client than relying on the
// peer id encoding.
if (auto sv = std::string_view{}; tr_variantDictFindStrView(&val, TR_KEY_v, &sv))
{
msgs->set_user_agent(tr_interned_string{ sv });
}
/* get peer's listening port */
if (tr_variantDictFindInt(&val, TR_KEY_p, &i))
{
@ -1269,8 +1225,6 @@ void parseLtep(tr_peerMsgsImpl* msgs, libtransmission::Buffer& payload)
}
}
using ReadResult = std::pair<ReadState, size_t /*n_piece_data_bytes_read*/>;
ReadResult process_peer_message(tr_peerMsgsImpl* msgs, uint8_t id, libtransmission::Buffer& payload);
void prefetchPieces(tr_peerMsgsImpl* msgs)
@ -1294,7 +1248,7 @@ void prefetchPieces(tr_peerMsgsImpl* msgs)
[[nodiscard]] bool canAddRequestFromPeer(tr_peerMsgsImpl const* const msgs, struct peer_request const& req)
{
if (msgs->peer_is_choked_)
if (msgs->peer_is_choked())
{
logtrace(msgs, "rejecting request from choked peer");
return false;
@ -1461,7 +1415,7 @@ ReadResult process_peer_message(tr_peerMsgsImpl* msgs, uint8_t id, libtransmissi
{
case BtPeerMsgs::Choke:
logtrace(msgs, "got Choke");
msgs->client_is_choked_ = true;
msgs->set_client_choked(true);
if (!fext)
{
@ -1473,20 +1427,20 @@ ReadResult process_peer_message(tr_peerMsgsImpl* msgs, uint8_t id, libtransmissi
case BtPeerMsgs::Unchoke:
logtrace(msgs, "got Unchoke");
msgs->client_is_choked_ = false;
msgs->set_client_choked(false);
msgs->update_active(TR_PEER_TO_CLIENT);
updateDesiredRequestCount(msgs);
break;
case BtPeerMsgs::Interested:
logtrace(msgs, "got Interested");
msgs->peer_is_interested_ = true;
msgs->set_peer_interested(true);
msgs->update_active(TR_CLIENT_TO_PEER);
break;
case BtPeerMsgs::NotInterested:
logtrace(msgs, "got Not Interested");
msgs->peer_is_interested_ = false;
msgs->set_peer_interested(false);
msgs->update_active(TR_CLIENT_TO_PEER);
break;
@ -1888,8 +1842,8 @@ void updateBlockRequests(tr_peerMsgsImpl* msgs)
return;
}
TR_ASSERT(msgs->is_client_interested());
TR_ASSERT(!msgs->is_client_choked());
TR_ASSERT(msgs->client_is_interested());
TR_ASSERT(!msgs->client_is_choked());
if (auto const requests = tr_peerMgrGetNextRequests(tor, msgs, n_wanted); !std::empty(requests))
{
@ -2272,8 +2226,9 @@ tr_peerMsgs* tr_peerMsgsNew(
tr_torrent* torrent,
peer_atom* atom,
std::shared_ptr<tr_peerIo> io,
tr_interned_string user_agent,
tr_peer_callback callback,
void* callback_data)
{
return new tr_peerMsgsImpl(torrent, atom, std::move(io), callback, callback_data);
return new tr_peerMsgsImpl(torrent, atom, std::move(io), user_agent, callback, callback_data);
}

View File

@ -10,19 +10,16 @@
#endif
#include <atomic>
#include <cstdint> // int8_t
#include <cstddef> // size_t
#include <ctime> // time_t
#include <cstddef> // for size_t
#include <memory>
#include <utility>
#include <utility> // for std::pair<>
#include "bitfield.h"
#include "peer-common.h"
#include "torrent.h"
#include "peer-common.h" // for tr_peer
class tr_peer;
class tr_peerIo;
struct tr_address;
struct tr_torrent;
/**
* @addtogroup peers Peers
@ -32,31 +29,73 @@ struct tr_address;
class tr_peerMsgs : public tr_peer
{
public:
tr_peerMsgs(tr_torrent const* tor, peer_atom* atom_in)
tr_peerMsgs(
tr_torrent const* tor,
peer_atom* atom_in,
tr_interned_string user_agent,
bool connection_is_encrypted,
bool connection_is_incoming,
bool connection_is_utp)
: tr_peer{ tor, atom_in }
, have_{ tor->pieceCount() }
, user_agent_{ user_agent }
, connection_is_encrypted_{ connection_is_encrypted }
, connection_is_incoming_{ connection_is_incoming }
, connection_is_utp_{ connection_is_utp }
{
++n_peers;
}
virtual ~tr_peerMsgs() override;
[[nodiscard]] static size_t size() noexcept
[[nodiscard]] static auto size() noexcept
{
return n_peers.load();
}
[[nodiscard]] virtual bool is_peer_choked() const noexcept = 0;
[[nodiscard]] virtual bool is_peer_interested() const noexcept = 0;
[[nodiscard]] virtual bool is_client_choked() const noexcept = 0;
[[nodiscard]] virtual bool is_client_interested() const noexcept = 0;
[[nodiscard]] constexpr auto client_is_choked() const noexcept
{
return client_is_choked_;
}
[[nodiscard]] virtual bool is_utp_connection() const noexcept = 0;
[[nodiscard]] virtual bool is_encrypted() const = 0;
[[nodiscard]] virtual bool is_incoming_connection() const = 0;
[[nodiscard]] constexpr auto client_is_interested() const noexcept
{
return client_is_interested_;
}
[[nodiscard]] virtual bool is_active(tr_direction direction) const = 0;
virtual void update_active(tr_direction direction) = 0;
[[nodiscard]] constexpr auto peer_is_choked() const noexcept
{
return peer_is_choked_;
}
[[nodiscard]] constexpr auto peer_is_interested() const noexcept
{
return peer_is_interested_;
}
[[nodiscard]] constexpr auto is_encrypted() const noexcept
{
return connection_is_encrypted_;
}
[[nodiscard]] constexpr auto is_incoming_connection() const noexcept
{
return connection_is_incoming_;
}
[[nodiscard]] constexpr auto is_utp_connection() const noexcept
{
return connection_is_utp_;
}
[[nodiscard]] constexpr auto const& user_agent() const noexcept
{
return user_agent_;
}
[[nodiscard]] constexpr auto is_active(tr_direction direction) const noexcept
{
return is_active_[direction];
}
[[nodiscard]] virtual std::pair<tr_address, tr_port> socketAddress() const = 0;
@ -71,20 +110,68 @@ public:
virtual void on_piece_completed(tr_piece_index_t) = 0;
/// The client name. This is the app name derived from the `v` string in LTEP's handshake dictionary
tr_interned_string client;
protected:
tr_bitfield have_;
constexpr void set_client_choked(bool val) noexcept
{
client_is_choked_ = val;
}
constexpr void set_client_interested(bool val) noexcept
{
client_is_interested_ = val;
}
constexpr void set_peer_choked(bool val) noexcept
{
peer_is_choked_ = val;
}
constexpr void set_peer_interested(bool val) noexcept
{
peer_is_interested_ = val;
}
constexpr void set_active(tr_direction direction, bool active) noexcept
{
is_active_[direction] = active;
}
constexpr void set_user_agent(tr_interned_string val) noexcept
{
user_agent_ = val;
}
private:
static inline auto n_peers = std::atomic<size_t>{};
// What software the peer is running.
// Derived from the `v` string in LTEP's handshake dictionary, when available.
tr_interned_string user_agent_;
bool const connection_is_encrypted_;
bool const connection_is_incoming_;
bool const connection_is_utp_;
std::array<bool, 2> is_active_ = {};
// whether or not the peer is choking us.
bool client_is_choked_ = true;
// whether or not we've indicated to the peer that we would download from them if unchoked
bool client_is_interested_ = false;
// whether or not we've choked this peer
bool peer_is_choked_ = true;
// whether or not the peer has indicated it will download from us
bool peer_is_interested_ = false;
};
tr_peerMsgs* tr_peerMsgsNew(
tr_torrent* torrent,
peer_atom* atom,
std::shared_ptr<tr_peerIo> io,
tr_interned_string user_agent,
tr_peer_callback callback,
void* callback_data);