diff --git a/libtransmission/handshake.cc b/libtransmission/handshake.cc index 91e0b9278..c4320e8b8 100644 --- a/libtransmission/handshake.cc +++ b/libtransmission/handshake.cc @@ -33,10 +33,6 @@ using namespace std::literals; /* DHT */ #define ENABLE_DHT -/*** -**** -***/ - static auto constexpr HandshakeName = std::array{ std::byte{ 19 }, std::byte{ 'B' }, std::byte{ 'i' }, std::byte{ 't' }, std::byte{ 'T' }, std::byte{ 'o' }, std::byte{ 'r' }, std::byte{ 'r' }, std::byte{ 'e' }, std::byte{ 'n' }, @@ -63,9 +59,6 @@ static auto constexpr CryptoProvideCrypto = int{ 2 }; using vc_t = std::array; static auto constexpr VC = vc_t{}; -// how long to wait before giving up on a handshake -static auto constexpr HandshakeTimeoutSec = 30s; - #ifdef ENABLE_LTEP #define HANDSHAKE_HAS_LTEP(bits) (((bits)[5] & 0x10) != 0) #define HANDSHAKE_SET_LTEP(bits) ((bits)[5] |= 0x10) @@ -94,132 +87,20 @@ static auto constexpr HandshakeTimeoutSec = 30s; *** **/ +#define tr_logAddTraceHand(handshake, msg) tr_logAddTrace(msg, (handshake)->display_name()) + using DH = tr_message_stream_encryption::DH; -enum handshake_state_t -{ - /* incoming */ - AWAITING_HANDSHAKE, - AWAITING_PEER_ID, - AWAITING_YA, - AWAITING_PAD_A, - AWAITING_CRYPTO_PROVIDE, - AWAITING_PAD_C, - AWAITING_IA, - AWAITING_PAYLOAD_STREAM, - /* outgoing */ - AWAITING_YB, - AWAITING_VC, - AWAITING_CRYPTO_SELECT, - AWAITING_PAD_D, - /* */ - N_STATES -}; - -struct tr_handshake -{ - tr_handshake(tr_handshake_mediator& mediator_in, std::shared_ptr io_in, tr_encryption_mode encryption_mode_in) - : mediator{ mediator_in } - , io{ std::move(io_in) } - , dh{ mediator.privateKey() } - , encryption_mode{ encryption_mode_in } - { - } - - tr_handshake(tr_handshake&&) = delete; - tr_handshake(tr_handshake const&) = delete; - tr_handshake& operator=(tr_handshake&&) = delete; - tr_handshake& operator=(tr_handshake const&) = delete; - ~tr_handshake() = default; - - [[nodiscard]] auto isIncoming() const noexcept - { - return io->isIncoming(); - } - - [[nodiscard]] constexpr uint32_t cryptoProvide() const - { - uint32_t provide = 0; - - switch (encryption_mode) - { - case TR_ENCRYPTION_REQUIRED: - case TR_ENCRYPTION_PREFERRED: - provide |= CryptoProvideCrypto; - break; - - case TR_CLEAR_PREFERRED: - provide |= CryptoProvideCrypto | CryptoProvidePlaintext; - break; - } - - return provide; - } - - tr_handshake_mediator& mediator; - - bool haveReadAnythingFromPeer = false; - bool haveSentBitTorrentHandshake = false; - std::shared_ptr const io; - DH dh = {}; - handshake_state_t state = AWAITING_HANDSHAKE; - tr_encryption_mode encryption_mode; - uint16_t pad_c_len = {}; - uint16_t pad_d_len = {}; - uint16_t ia_len = {}; - uint32_t crypto_select = {}; - uint32_t crypto_provide = {}; - std::unique_ptr timeout_timer; - - std::optional peer_id; - - tr_handshake_done_func done_func = nullptr; - void* done_func_user_data = nullptr; -}; - /** *** **/ -#define tr_logAddTraceHand(handshake, msg) tr_logAddTrace(msg, (handshake)->io->display_name()) - -static constexpr std::string_view getStateName(handshake_state_t const state) +bool tr_handshake::build_handshake_message(tr_peerIo* io, uint8_t* buf) const { - auto state_strings = std::array{ - "awaiting handshake"sv, /* AWAITING_HANDSHAKE */ - "awaiting peer id"sv, /* AWAITING_PEER_ID */ - "awaiting ya"sv, /* AWAITING_YA */ - "awaiting pad a"sv, /* AWAITING_PAD_A */ - "awaiting crypto_provide"sv, /* AWAITING_CRYPTO_PROVIDE */ - "awaiting pad c"sv, /* AWAITING_PAD_C */ - "awaiting ia"sv, /* AWAITING_IA */ - "awaiting payload stream"sv, /* AWAITING_PAYLOAD_STREAM */ - "awaiting yb"sv, /* AWAITING_YB */ - "awaiting vc"sv, /* AWAITING_VC */ - "awaiting crypto select"sv, /* AWAITING_CRYPTO_SELECT */ - "awaiting pad d"sv /* AWAITING_PAD_D */ - }; + auto const& info_hash = io->torrentHash(); + TR_ASSERT_MSG(info_hash != tr_sha1_digest_t{}, "build_handshake_message requires an info_hash"); - return state < N_STATES ? state_strings[state] : "unknown state"sv; -} - -static void setState(tr_handshake* handshake, handshake_state_t state) -{ - tr_logAddTraceHand(handshake, fmt::format("setting to state [{}]", getStateName(state))); - handshake->state = state; -} - -static void setReadState(tr_handshake* handshake, handshake_state_t state) -{ - setState(handshake, state); -} - -static bool buildHandshakeMessage(tr_handshake const* const handshake, uint8_t* buf) -{ - auto const& info_hash = handshake->io->torrentHash(); - TR_ASSERT_MSG(info_hash != tr_sha1_digest_t{}, "buildHandshakeMessage requires an info_hash"); - - auto const info = handshake->mediator.torrentInfo(info_hash); + auto const info = mediator_->torrent_info(info_hash); if (!info) { return false; @@ -235,7 +116,7 @@ static bool buildHandshakeMessage(tr_handshake const* const handshake, uint8_t* /* Note that this doesn't depend on whether the torrent is private. * We don't accept DHT peers for a private torrent, * but we participate in the DHT regardless. */ - if (handshake->mediator.allowsDHT()) + if (mediator_->allows_dht()) { HANDSHAKE_SET_DHT(walk); } @@ -251,19 +132,9 @@ static bool buildHandshakeMessage(tr_handshake const* const handshake, uint8_t* return true; } -static ReadState tr_handshakeDone(tr_handshake* handshake, bool is_connected); - -enum class ParseResult +tr_handshake::ParseResult tr_handshake::parse_handshake(tr_peerIo* peer_io) { - Ok, - EncryptionWrong, - BadTorrent, - PeerIsSelf, -}; - -static ParseResult parseHandshake(tr_handshake* handshake, tr_peerIo* peer_io) -{ - tr_logAddTraceHand(handshake, fmt::format("payload: need {}, got {}", HandshakeSize, peer_io->readBufferSize())); + tr_logAddTraceHand(this, fmt::format("payload: need {}, got {}", HandshakeSize, peer_io->readBufferSize())); if (peer_io->readBufferSize() < HandshakeSize) { @@ -287,29 +158,26 @@ static ParseResult parseHandshake(tr_handshake* handshake, tr_peerIo* peer_io) peer_io->readBytes(std::data(info_hash), std::size(info_hash)); if (info_hash == tr_sha1_digest_t{} || info_hash != peer_io->torrentHash()) { - tr_logAddTraceHand(handshake, "peer returned the wrong hash. wtf?"); + tr_logAddTraceHand(this, "peer returned the wrong hash. wtf?"); return ParseResult::BadTorrent; } // peer_id auto peer_id = tr_peer_id_t{}; peer_io->readBytes(std::data(peer_id), std::size(peer_id)); - handshake->peer_id = peer_id; + set_peer_id(peer_id); /* peer id */ auto const peer_id_sv = std::string_view{ std::data(peer_id), std::size(peer_id) }; - tr_logAddTraceHand(handshake, fmt::format("peer-id is '{}'", peer_id_sv)); + tr_logAddTraceHand(this, fmt::format("peer-id is '{}'", peer_id_sv)); - if (auto const info = handshake->mediator.torrentInfo(info_hash); info && info->client_peer_id == peer_id) + if (auto const info = mediator_->torrent_info(info_hash); info && info->client_peer_id == peer_id) { - tr_logAddTraceHand(handshake, "streuth! we've connected to ourselves."); + tr_logAddTraceHand(this, "streuth! we've connected to ourselves."); return ParseResult::PeerIsSelf; } - /** - *** Extensions - **/ - + // extensions peer_io->enableDHT(HANDSHAKE_HAS_DHT(reserved)); peer_io->enableLTEP(HANDSHAKE_HAS_LTEP(reserved)); peer_io->enableFEXT(HANDSHAKE_HAS_FASTEXT(reserved)); @@ -323,23 +191,11 @@ static ParseResult parseHandshake(tr_handshake* handshake, tr_peerIo* peer_io) **** ***/ -template -static void sendPublicKeyAndPad(tr_handshake* handshake) -{ - auto const public_key = handshake->dh.publicKey(); - auto outbuf = std::array{}; - auto const data = std::data(outbuf); - auto walk = data; - walk = std::copy(std::begin(public_key), std::end(public_key), walk); - walk += handshake->mediator.pad(walk, PadMax); - handshake->io->writeBytes(data, walk - data, false); -} - // 1 A->B: our public key (Ya) and some padding (PadA) -static void sendYa(tr_handshake* handshake) +void tr_handshake::send_ya(tr_peerIo* io) { - sendPublicKeyAndPad(handshake); - setReadState(handshake, AWAITING_YB); + send_public_key_and_pad(io); + set_state(tr_handshake::State::AwaitingYb); } static constexpr uint32_t getCryptoSelect(tr_encryption_mode encryption_mode, uint32_t crypto_provide) @@ -375,7 +231,7 @@ static constexpr uint32_t getCryptoSelect(tr_encryption_mode encryption_mode, ui return 0; } -static ReadState readYb(tr_handshake* handshake, tr_peerIo* peer_io) +ReadState tr_handshake::read_yb(tr_peerIo* peer_io) { if (peer_io->readBufferSize() < std::size(HandshakeName)) { @@ -389,26 +245,26 @@ static ReadState readYb(tr_handshake* handshake, tr_peerIo* peer_io) return READ_LATER; } - tr_logAddTraceHand(handshake, is_encrypted ? "got an encrypted handshake" : "got a plain handshake"); + tr_logAddTraceHand(this, is_encrypted ? "got an encrypted handshake" : "got a plain handshake"); if (!is_encrypted) { - setState(handshake, AWAITING_HANDSHAKE); + set_state(tr_handshake::State::AwaitingHandshake); return READ_NOW; } - handshake->haveReadAnythingFromPeer = true; + set_have_read_anything_from_peer(true); // get the peer's public key peer_io->readBytes(std::data(peer_public_key), std::size(peer_public_key)); - handshake->dh.setPeerPublicKey(peer_public_key); + dh_.setPeerPublicKey(peer_public_key); /* now send these: HASH('req1', S), HASH('req2', SKEY) xor HASH('req3', S), * ENCRYPT(VC, crypto_provide, len(PadC), PadC, len(IA)), ENCRYPT(IA) */ auto outbuf = libtransmission::Buffer{}; /* HASH('req1', S) */ - outbuf.add(tr_sha1::digest("req1"sv, handshake->dh.secret())); + outbuf.add(tr_sha1::digest("req1"sv, dh_.secret())); auto const& info_hash = peer_io->torrentHash(); TR_ASSERT_MSG(info_hash != tr_sha1_digest_t{}, "readYb requires an info_hash"); @@ -416,7 +272,7 @@ static ReadState readYb(tr_handshake* handshake, tr_peerIo* peer_io) /* HASH('req2', SKEY) xor HASH('req3', S) */ { auto const req2 = tr_sha1::digest("req2"sv, info_hash); - auto const req3 = tr_sha1::digest("req3"sv, handshake->dh.secret()); + auto const req3 = tr_sha1::digest("req3"sv, dh_.secret()); auto x_or = tr_sha1_digest_t{}; for (size_t i = 0, n = std::size(x_or); i < n; ++i) { @@ -430,32 +286,32 @@ static ReadState readYb(tr_handshake* handshake, tr_peerIo* peer_io) * PadC is reserved for future extensions to the handshake... * standard practice at this time is for it to be zero-length */ peer_io->write(outbuf, false); - peer_io->encryptInit(peer_io->isIncoming(), handshake->dh, info_hash); + peer_io->encryptInit(peer_io->isIncoming(), dh_, info_hash); outbuf.add(VC); - outbuf.addUint32(handshake->cryptoProvide()); + outbuf.addUint32(crypto_provide()); outbuf.addUint16(0); /* ENCRYPT len(IA)), ENCRYPT(IA) */ - if (auto msg = std::array{}; buildHandshakeMessage(handshake, std::data(msg))) + if (auto msg = std::array{}; build_handshake_message(peer_io, std::data(msg))) { outbuf.addUint16(std::size(msg)); outbuf.add(msg); - handshake->haveSentBitTorrentHandshake = true; + have_sent_bittorrent_handshake_ = true; } else { - return tr_handshakeDone(handshake, false); + return done(false); } /* send it */ - setReadState(handshake, AWAITING_VC); + set_state(State::AwaitingVc); peer_io->write(outbuf, false); return READ_NOW; } // MSE spec: "Since the length of [PadB is] unknown, // A will be able to resynchronize on ENCRYPT(VC)" -static ReadState readVC(tr_handshake* handshake, tr_peerIo* peer_io) +ReadState tr_handshake::read_vc(tr_peerIo* peer_io) { auto const info_hash = peer_io->torrentHash(); TR_ASSERT_MSG(info_hash != tr_sha1_digest_t{}, "readVC requires an info_hash"); @@ -463,74 +319,74 @@ static ReadState readVC(tr_handshake* handshake, tr_peerIo* peer_io) // find the end of PadB by looking for `ENCRYPT(VC)` auto needle = VC; auto filter = tr_message_stream_encryption::Filter{}; - filter.encryptInit(true, handshake->dh, info_hash); + filter.encryptInit(true, dh_, info_hash); filter.encrypt(std::size(needle), std::data(needle)); for (size_t i = 0; i < PadbMaxlen; ++i) { if (peer_io->readBufferSize() < std::size(needle)) { - tr_logAddTraceHand(handshake, "not enough bytes... returning read_more"); + tr_logAddTraceHand(this, "not enough bytes... returning read_more"); return READ_LATER; } if (peer_io->readBufferStartsWith(needle)) { - tr_logAddTraceHand(handshake, "got it!"); + tr_logAddTraceHand(this, "got it!"); // We already know it's a match; now we just need to // consume it from the read buffer. - peer_io->decryptInit(peer_io->isIncoming(), handshake->dh, info_hash); + peer_io->decryptInit(peer_io->isIncoming(), dh_, info_hash); peer_io->readBytes(std::data(needle), std::size(needle)); - setState(handshake, AWAITING_CRYPTO_SELECT); + set_state(tr_handshake::State::AwaitingCryptoSelect); return READ_NOW; } peer_io->readBufferDrain(1); } - tr_logAddTraceHand(handshake, "couldn't find ENCRYPT(VC)"); - return tr_handshakeDone(handshake, false); + tr_logAddTraceHand(this, "couldn't find ENCRYPT(VC)"); + return done(false); } -static ReadState readCryptoSelect(tr_handshake* handshake, tr_peerIo* peer_io) +ReadState tr_handshake::read_crypto_select(tr_peerIo* peer_io) { if (static size_t constexpr NeedLen = sizeof(uint32_t) + sizeof(uint16_t); peer_io->readBufferSize() < NeedLen) { return READ_LATER; } - uint32_t crypto_select = 0; + auto crypto_select = uint32_t{}; peer_io->readUint32(&crypto_select); - handshake->crypto_select = crypto_select; - tr_logAddTraceHand(handshake, fmt::format("crypto select is {}", crypto_select)); + crypto_select_ = crypto_select; + tr_logAddTraceHand(this, fmt::format("crypto select is {}", crypto_select)); - if ((crypto_select & handshake->cryptoProvide()) == 0) + if ((crypto_select & crypto_provide()) == 0) { - tr_logAddTraceHand(handshake, "peer selected an encryption option we didn't offer"); - return tr_handshakeDone(handshake, false); + tr_logAddTraceHand(this, "peer selected an encryption option we didn't offer"); + return done(false); } uint16_t pad_d_len = 0; peer_io->readUint16(&pad_d_len); - tr_logAddTraceHand(handshake, fmt::format("pad_d_len is {}", pad_d_len)); + tr_logAddTraceHand(this, fmt::format("pad_d_len is {}", pad_d_len)); if (pad_d_len > 512) { - tr_logAddTraceHand(handshake, "encryption handshake: pad_d_len is too long"); - return tr_handshakeDone(handshake, false); + tr_logAddTraceHand(this, "encryption handshake: pad_d_len is too long"); + return done(false); } - handshake->pad_d_len = pad_d_len; + pad_d_len_ = pad_d_len; - setState(handshake, AWAITING_PAD_D); + set_state(tr_handshake::State::AwaitingPadD); return READ_NOW; } -static ReadState readPadD(tr_handshake* handshake, tr_peerIo* peer_io) +ReadState tr_handshake::read_pad_d(tr_peerIo* peer_io) { - size_t const needlen = handshake->pad_d_len; + size_t const needlen = pad_d_len_; - tr_logAddTraceHand(handshake, fmt::format("pad d: need {}, got {}", needlen, peer_io->readBufferSize())); + tr_logAddTraceHand(this, fmt::format("pad d: need {}, got {}", needlen, peer_io->readBufferSize())); if (peer_io->readBufferSize() < needlen) { @@ -539,7 +395,7 @@ static ReadState readPadD(tr_handshake* handshake, tr_peerIo* peer_io) peer_io->readBufferDrain(needlen); - setState(handshake, AWAITING_HANDSHAKE); + set_state(tr_handshake::State::AwaitingHandshake); return READ_NOW; } @@ -549,31 +405,31 @@ static ReadState readPadD(tr_handshake* handshake, tr_peerIo* peer_io) **** ***/ -static ReadState readHandshake(tr_handshake* handshake, tr_peerIo* peer_io) +ReadState tr_handshake::read_handshake(tr_peerIo* peer_io) { - tr_logAddTraceHand(handshake, fmt::format("payload: need {}, got {}", IncomingHandshakeLen, peer_io->readBufferSize())); + tr_logAddTraceHand(this, fmt::format("payload: need {}, got {}", IncomingHandshakeLen, peer_io->readBufferSize())); if (peer_io->readBufferSize() < IncomingHandshakeLen) { return READ_LATER; } - handshake->haveReadAnythingFromPeer = true; + set_have_read_anything_from_peer(true); if (peer_io->readBufferStartsWith(HandshakeName)) // unencrypted { - if (handshake->encryption_mode == TR_ENCRYPTION_REQUIRED) + if (encryption_mode_ == TR_ENCRYPTION_REQUIRED) { - tr_logAddTraceHand(handshake, "peer is unencrypted, and we're disallowing that"); - return tr_handshakeDone(handshake, false); + tr_logAddTraceHand(this, "peer is unencrypted, and we're disallowing that"); + return done(false); } } else // either encrypted or corrupt { - if (handshake->isIncoming()) + if (is_incoming()) { - tr_logAddTraceHand(handshake, "I think peer is sending us an encrypted handshake..."); - setState(handshake, AWAITING_YA); + tr_logAddTraceHand(this, "I think peer is sending us an encrypted handshake..."); + set_state(tr_handshake::State::AwaitingYa); return READ_NOW; } } @@ -582,7 +438,7 @@ static ReadState readHandshake(tr_handshake* handshake, tr_peerIo* peer_io) peer_io->readBytes(std::data(name), std::size(name)); if (name != HandshakeName) { - return tr_handshakeDone(handshake, false); + return done(false); } /* reserved bytes */ @@ -601,12 +457,12 @@ static ReadState readHandshake(tr_handshake* handshake, tr_peerIo* peer_io) auto hash = tr_sha1_digest_t{}; peer_io->readBytes(std::data(hash), std::size(hash)); - if (handshake->isIncoming()) + if (is_incoming()) { - if (!handshake->mediator.torrentInfo(hash)) + if (!mediator_->torrent_info(hash)) { - tr_logAddTraceHand(handshake, "peer is trying to connect to us for a torrent we don't have."); - return tr_handshakeDone(handshake, false); + tr_logAddTraceHand(this, "peer is trying to connect to us for a torrent we don't have."); + return done(false); } peer_io->setTorrentHash(hash); @@ -615,8 +471,8 @@ static ReadState readHandshake(tr_handshake* handshake, tr_peerIo* peer_io) { if (peer_io->torrentHash() != hash) { - tr_logAddTraceHand(handshake, "peer returned the wrong hash. wtf?"); - return tr_handshakeDone(handshake, false); + tr_logAddTraceHand(this, "peer returned the wrong hash. wtf?"); + return done(false); } } @@ -624,24 +480,24 @@ static ReadState readHandshake(tr_handshake* handshake, tr_peerIo* peer_io) *** If it's an incoming message, we need to send a response handshake **/ - if (!handshake->haveSentBitTorrentHandshake) + if (!have_sent_bittorrent_handshake_) { auto msg = std::array{}; - if (!buildHandshakeMessage(handshake, std::data(msg))) + if (!build_handshake_message(peer_io, std::data(msg))) { - return tr_handshakeDone(handshake, false); + return done(false); } peer_io->writeBytes(std::data(msg), std::size(msg), false); - handshake->haveSentBitTorrentHandshake = true; + have_sent_bittorrent_handshake_ = true; } - setReadState(handshake, AWAITING_PEER_ID); + set_state(State::AwaitingPeerId); return READ_NOW; } -static ReadState readPeerId(tr_handshake* handshake, tr_peerIo* peer_io) +ReadState tr_handshake::read_peer_id(tr_peerIo* peer_io) { // read the peer_id auto peer_id = tr_peer_id_t{}; @@ -650,27 +506,25 @@ static ReadState readPeerId(tr_handshake* handshake, tr_peerIo* peer_io) return READ_LATER; } peer_io->readBytes(std::data(peer_id), std::size(peer_id)); - handshake->peer_id = peer_id; + set_peer_id(peer_id); auto client = std::array{}; tr_clientForId(std::data(client), std::size(client), peer_id); - tr_logAddTraceHand( - handshake, - fmt::format("peer-id is '{}' ... isIncoming is {}", std::data(client), handshake->isIncoming())); + tr_logAddTraceHand(this, fmt::format("peer-id is '{}' ... isIncoming is {}", std::data(client), is_incoming())); // if we've somehow connected to ourselves, don't keep the connection - auto const info_hash = peer_io->torrentHash(); - auto const info = handshake->mediator.torrentInfo(info_hash); + auto const info_hash = peer_io_->torrentHash(); + auto const info = mediator_->torrent_info(info_hash); auto const connected_to_self = info && info->client_peer_id == peer_id; - return tr_handshakeDone(handshake, !connected_to_self); + return done(!connected_to_self); } -static ReadState readYa(tr_handshake* handshake, tr_peerIo* peer_io) +ReadState tr_handshake::read_ya(tr_peerIo* peer_io) { auto peer_public_key = DH::key_bigend_t{}; tr_logAddTraceHand( - handshake, + this, fmt::format("in readYa... need {}, have {}", std::size(peer_public_key), peer_io->readBufferSize())); if (peer_io->readBufferSize() < std::size(peer_public_key)) @@ -680,45 +534,45 @@ static ReadState readYa(tr_handshake* handshake, tr_peerIo* peer_io) /* read the incoming peer's public key */ peer_io->readBytes(std::data(peer_public_key), std::size(peer_public_key)); - handshake->dh.setPeerPublicKey(peer_public_key); + dh_.setPeerPublicKey(peer_public_key); // send our public key to the peer - tr_logAddTraceHand(handshake, "sending B->A: Diffie Hellman Yb, PadB"); - sendPublicKeyAndPad(handshake); + tr_logAddTraceHand(this, "sending B->A: Diffie Hellman Yb, PadB"); + send_public_key_and_pad(peer_io); - setReadState(handshake, AWAITING_PAD_A); + set_state(State::AwaitingPadA); return READ_NOW; } -static ReadState readPadA(tr_handshake* handshake, tr_peerIo* peer_io) +ReadState tr_handshake::read_pad_a(tr_peerIo* peer_io) { // find the end of PadA by looking for HASH('req1', S) - auto const needle = tr_sha1::digest("req1"sv, handshake->dh.secret()); + auto const needle = tr_sha1::digest("req1"sv, dh_.secret()); for (size_t i = 0; i < PadaMaxlen; ++i) { if (peer_io->readBufferSize() < std::size(needle)) { - tr_logAddTraceHand(handshake, "not enough bytes... returning read_more"); + tr_logAddTraceHand(this, "not enough bytes... returning read_more"); return READ_LATER; } if (peer_io->readBufferStartsWith(needle)) { - tr_logAddTraceHand(handshake, "found it... looking setting to awaiting_crypto_provide"); + tr_logAddTraceHand(this, "found it... looking setting to awaiting_crypto_provide"); peer_io->readBufferDrain(std::size(needle)); - setState(handshake, AWAITING_CRYPTO_PROVIDE); + set_state(State::AwaitingCryptoProvide); return READ_NOW; } peer_io->readBufferDrain(1U); } - tr_logAddTraceHand(handshake, "couldn't find HASH('req', S)"); - return tr_handshakeDone(handshake, false); + tr_logAddTraceHand(this, "couldn't find HASH('req', S)"); + return done(false); } -static ReadState readCryptoProvide(tr_handshake* handshake, tr_peerIo* peer_io) +ReadState tr_handshake::read_crypto_provide(tr_peerIo* peer_io) { /* HASH('req2', SKEY) xor HASH('req3', S), ENCRYPT(VC, crypto_provide, len(PadC)) */ @@ -736,86 +590,86 @@ static ReadState readCryptoProvide(tr_handshake* handshake, tr_peerIo* peer_io) /* This next piece is HASH('req2', SKEY) xor HASH('req3', S) ... * we can get the first half of that (the obfuscatedTorrentHash) * by building the latter and xor'ing it with what the peer sent us */ - tr_logAddTraceHand(handshake, "reading obfuscated torrent hash..."); + tr_logAddTraceHand(this, "reading obfuscated torrent hash..."); auto req2 = tr_sha1_digest_t{}; peer_io->readBytes(std::data(req2), std::size(req2)); - auto const req3 = tr_sha1::digest("req3"sv, handshake->dh.secret()); + auto const req3 = tr_sha1::digest("req3"sv, dh_.secret()); for (size_t i = 0; i < std::size(obfuscated_hash); ++i) { obfuscated_hash[i] = req2[i] ^ req3[i]; } - if (auto const info = handshake->mediator.torrentInfoFromObfuscated(obfuscated_hash); info) + if (auto const info = mediator_->torrent_info_from_obfuscated(obfuscated_hash); info) { bool const client_is_seed = info->is_done; - bool const peer_is_seed = handshake->mediator.isPeerKnownSeed(info->id, peer_io->address()); - tr_logAddTraceHand(handshake, fmt::format("got INCOMING connection's encrypted handshake for torrent [{}]", info->id)); + bool const peer_is_seed = mediator_->is_peer_known_seed(info->id, peer_io->address()); + tr_logAddTraceHand(this, fmt::format("got INCOMING connection's encrypted handshake for torrent [{}]", info->id)); peer_io->setTorrentHash(info->info_hash); if (client_is_seed && peer_is_seed) { - tr_logAddTraceHand(handshake, "another seed tried to reconnect to us!"); - return tr_handshakeDone(handshake, false); + tr_logAddTraceHand(this, "another seed tried to reconnect to us!"); + return done(false); } } else { - tr_logAddTraceHand(handshake, "can't find that torrent..."); - return tr_handshakeDone(handshake, false); + tr_logAddTraceHand(this, "can't find that torrent..."); + return done(false); } /* next part: ENCRYPT(VC, crypto_provide, len(PadC), */ auto const& info_hash = peer_io->torrentHash(); TR_ASSERT_MSG(info_hash != tr_sha1_digest_t{}, "readCryptoProvide requires an info_hash"); - peer_io->decryptInit(peer_io->isIncoming(), handshake->dh, info_hash); + peer_io->decryptInit(peer_io->isIncoming(), dh_, info_hash); auto vc_in = vc_t{}; peer_io->readBytes(std::data(vc_in), std::size(vc_in)); peer_io->readUint32(&crypto_provide); - handshake->crypto_provide = crypto_provide; - tr_logAddTraceHand(handshake, fmt::format("crypto_provide is {}", crypto_provide)); + crypto_provide_ = crypto_provide; + tr_logAddTraceHand(this, fmt::format("crypto_provide is {}", crypto_provide)); peer_io->readUint16(&padc_len); - tr_logAddTraceHand(handshake, fmt::format("padc is {}", padc_len)); + tr_logAddTraceHand(this, fmt::format("padc is {}", padc_len)); if (padc_len > PadcMaxlen) { - tr_logAddTraceHand(handshake, "peer's PadC is too big"); - return tr_handshakeDone(handshake, false); + tr_logAddTraceHand(this, "peer's PadC is too big"); + return done(false); } - handshake->pad_c_len = padc_len; - setState(handshake, AWAITING_PAD_C); + pad_c_len_ = padc_len; + set_state(State::AwaitingPadC); return READ_NOW; } -static ReadState readPadC(tr_handshake* handshake, tr_peerIo* peer_io) +ReadState tr_handshake::read_pad_c(tr_peerIo* peer_io) { - if (auto const needlen = handshake->pad_c_len + sizeof(uint16_t); peer_io->readBufferSize() < needlen) + if (auto const needlen = pad_c_len_ + sizeof(uint16_t); peer_io->readBufferSize() < needlen) { return READ_LATER; } // read the throwaway padc auto pad_c = std::array{}; - peer_io->readBytes(std::data(pad_c), handshake->pad_c_len); + peer_io->readBytes(std::data(pad_c), pad_c_len_); /* read ia_len */ uint16_t ia_len = 0; peer_io->readUint16(&ia_len); - tr_logAddTraceHand(handshake, fmt::format("ia_len is {}", ia_len)); - handshake->ia_len = ia_len; - setState(handshake, AWAITING_IA); + tr_logAddTraceHand(this, fmt::format("ia_len is {}", ia_len)); + ia_len_ = ia_len; + set_state(State::AwaitingIa); return READ_NOW; } -static ReadState readIA(tr_handshake* handshake, tr_peerIo* peer_io) +ReadState tr_handshake::read_ia(tr_peerIo* peer_io) { - size_t const needlen = handshake->ia_len; + size_t const needlen = ia_len_; - tr_logAddTraceHand(handshake, fmt::format("reading IA... have {}, need {}", peer_io->readBufferSize(), needlen)); + tr_logAddTraceHand(this, fmt::format("reading IA... have {}, need {}", peer_io->readBufferSize(), needlen)); if (peer_io->readBufferSize() < needlen) { @@ -828,28 +682,28 @@ static ReadState readIA(tr_handshake* handshake, tr_peerIo* peer_io) auto const& info_hash = peer_io->torrentHash(); TR_ASSERT_MSG(info_hash != tr_sha1_digest_t{}, "readIA requires an info_hash"); - peer_io->encryptInit(peer_io->isIncoming(), handshake->dh, info_hash); + peer_io->encryptInit(peer_io->isIncoming(), dh_, info_hash); auto outbuf = libtransmission::Buffer{}; // send VC - tr_logAddTraceHand(handshake, "sending vc"); + tr_logAddTraceHand(this, "sending vc"); outbuf.add(VC); /* send crypto_select */ - uint32_t const crypto_select = getCryptoSelect(handshake->encryption_mode, handshake->crypto_provide); + uint32_t const crypto_select = getCryptoSelect(encryption_mode_, crypto_provide_); if (crypto_select != 0) { - tr_logAddTraceHand(handshake, fmt::format("selecting crypto mode '{}'", crypto_select)); + tr_logAddTraceHand(this, fmt::format("selecting crypto mode '{}'", crypto_select)); outbuf.addUint32(crypto_select); } else { - tr_logAddTraceHand(handshake, "peer didn't offer an encryption mode we like."); - return tr_handshakeDone(handshake, false); + tr_logAddTraceHand(this, "peer didn't offer an encryption mode we like."); + return done(false); } - tr_logAddTraceHand(handshake, "sending pad d"); + tr_logAddTraceHand(this, "sending pad d"); /* ENCRYPT(VC, crypto_provide, len(PadD), PadD * PadD is reserved for future extensions to the handshake... @@ -863,34 +717,32 @@ static ReadState readIA(tr_handshake* handshake, tr_peerIo* peer_io) TR_ASSERT(std::empty(outbuf)); } - tr_logAddTraceHand(handshake, "sending handshake"); + tr_logAddTraceHand(this, "sending handshake"); /* send our handshake */ - if (auto msg = std::array{}; buildHandshakeMessage(handshake, std::data(msg))) + if (auto msg = std::array{}; build_handshake_message(peer_io, std::data(msg))) { outbuf.add(msg); - handshake->haveSentBitTorrentHandshake = true; + have_sent_bittorrent_handshake_ = true; } else { - return tr_handshakeDone(handshake, false); + return done(false); } /* send it out */ peer_io->write(outbuf, false); /* now await the handshake */ - setState(handshake, AWAITING_PAYLOAD_STREAM); + set_state(State::AwaitingPayloadStream); return READ_NOW; } -static ReadState readPayloadStream(tr_handshake* handshake, tr_peerIo* peer_io) +ReadState tr_handshake::read_payload_stream(tr_peerIo* peer_io) { size_t const needlen = HandshakeSize; - tr_logAddTraceHand( - handshake, - fmt::format("reading payload stream... have {}, need {}", peer_io->readBufferSize(), needlen)); + tr_logAddTraceHand(this, fmt::format("reading payload stream... have {}, need {}", peer_io->readBufferSize(), needlen)); if (peer_io->readBufferSize() < needlen) { @@ -898,16 +750,16 @@ static ReadState readPayloadStream(tr_handshake* handshake, tr_peerIo* peer_io) } /* parse the handshake ... */ - auto const i = parseHandshake(handshake, peer_io); - tr_logAddTraceHand(handshake, fmt::format("parseHandshake returned {}", static_cast(i))); + auto const i = parse_handshake(peer_io); + tr_logAddTraceHand(this, fmt::format("parseHandshake returned {}", static_cast(i))); if (i != ParseResult::Ok) { - return tr_handshakeDone(handshake, false); + return done(false); } /* we've completed the BT handshake... pass the work on to peer-msgs */ - return tr_handshakeDone(handshake, true); + return done(true); } /*** @@ -916,7 +768,7 @@ static ReadState readPayloadStream(tr_handshake* handshake, tr_peerIo* peer_io) **** ***/ -static ReadState canRead(tr_peerIo* peer_io, void* vhandshake, size_t* piece) +ReadState tr_handshake::can_read(tr_peerIo* peer_io, void* vhandshake, size_t* piece) { TR_ASSERT(tr_isPeerIo(peer_io)); @@ -927,64 +779,66 @@ static ReadState canRead(tr_peerIo* peer_io, void* vhandshake, size_t* piece) /* no piece data in handshake */ *piece = 0; - tr_logAddTraceHand(handshake, fmt::format("handling canRead; state is [{}]", getStateName(handshake->state))); + tr_logAddTraceHand(handshake, fmt::format("handling canRead; state is [{}]", handshake->state_string())); ReadState ret = READ_NOW; while (ready_for_more) { - switch (handshake->state) + switch (handshake->state()) { - case AWAITING_HANDSHAKE: - ret = readHandshake(handshake, peer_io); + case State::AwaitingHandshake: + ret = handshake->read_handshake(peer_io); break; - case AWAITING_PEER_ID: - ret = readPeerId(handshake, peer_io); + case State::AwaitingPeerId: + ret = handshake->read_peer_id(peer_io); break; - case AWAITING_YA: - ret = readYa(handshake, peer_io); + case State::AwaitingYa: + ret = handshake->read_ya(peer_io); break; - case AWAITING_PAD_A: - ret = readPadA(handshake, peer_io); + case State::AwaitingPadA: + ret = handshake->read_pad_a(peer_io); break; - case AWAITING_CRYPTO_PROVIDE: - ret = readCryptoProvide(handshake, peer_io); + case State::AwaitingCryptoProvide: + ret = handshake->read_crypto_provide(peer_io); break; - case AWAITING_PAD_C: - ret = readPadC(handshake, peer_io); + case State::AwaitingPadC: + ret = handshake->read_pad_c(peer_io); break; - case AWAITING_IA: - ret = readIA(handshake, peer_io); + case State::AwaitingIa: + ret = handshake->read_ia(peer_io); break; - case AWAITING_PAYLOAD_STREAM: - ret = readPayloadStream(handshake, peer_io); + case State::AwaitingPayloadStream: + ret = handshake->read_payload_stream(peer_io); break; - case AWAITING_YB: - ret = readYb(handshake, peer_io); + case State::AwaitingYb: + ret = handshake->read_yb(peer_io); break; - case AWAITING_VC: - ret = readVC(handshake, peer_io); + case State::AwaitingVc: + ret = handshake->read_vc(peer_io); break; - case AWAITING_CRYPTO_SELECT: - ret = readCryptoSelect(handshake, peer_io); + case State::AwaitingCryptoSelect: + ret = handshake->read_crypto_select(peer_io); break; - case AWAITING_PAD_D: - ret = readPadD(handshake, peer_io); + case State::AwaitingPadD: + ret = handshake->read_pad_d(peer_io); break; default: #ifdef TR_ENABLE_ASSERTS - TR_ASSERT_MSG(false, fmt::format(FMT_STRING("unhandled handshake state {:d}"), static_cast(handshake->state))); + TR_ASSERT_MSG( + false, + fmt::format(FMT_STRING("unhandled handshake state {:d}"), static_cast(handshake->state()))); #else ret = READ_ERR; break; @@ -995,102 +849,70 @@ static ReadState canRead(tr_peerIo* peer_io, void* vhandshake, size_t* piece) { ready_for_more = false; } - else if (handshake->state == AWAITING_PAD_C) + else if (handshake->is_state(State::AwaitingPadC)) { - ready_for_more = peer_io->readBufferSize() >= handshake->pad_c_len; + ready_for_more = peer_io->readBufferSize() >= handshake->pad_c_len_; } - else if (handshake->state == AWAITING_PAD_D) + else if (handshake->is_state(State::AwaitingPadD)) { - ready_for_more = peer_io->readBufferSize() >= handshake->pad_d_len; + ready_for_more = peer_io->readBufferSize() >= handshake->pad_d_len_; } - else if (handshake->state == AWAITING_IA) + else if (handshake->is_state(State::AwaitingIa)) { - ready_for_more = peer_io->readBufferSize() >= handshake->ia_len; + ready_for_more = peer_io->readBufferSize() >= handshake->ia_len_; } } return ret; } -static bool fireDoneFunc(tr_handshake* handshake, bool is_connected) -{ - auto result = tr_handshake_result{}; - result.handshake = handshake; - result.io = handshake->io; - result.readAnythingFromPeer = handshake->haveReadAnythingFromPeer; - result.isConnected = is_connected; - result.userData = handshake->done_func_user_data; - result.peer_id = handshake->peer_id; - bool const success = (*handshake->done_func)(result); - return success; -} - -static ReadState tr_handshakeDone(tr_handshake* handshake, bool is_connected) -{ - tr_logAddTraceHand(handshake, is_connected ? "handshakeDone: connected" : "handshakeDone: aborting"); - handshake->io->clearCallbacks(); - - bool const success = fireDoneFunc(handshake, is_connected); - delete handshake; - return success ? READ_LATER : READ_ERR; -} - -void tr_handshakeAbort(tr_handshake* handshake) -{ - if (handshake != nullptr) - { - tr_handshakeDone(handshake, false); - } -} - -static void gotError(tr_peerIo* io, short what, void* vhandshake) +void tr_handshake::on_error(tr_peerIo* io, short what, void* vhandshake) { int const errcode = errno; auto* handshake = static_cast(vhandshake); - if (io->socket.is_utp() && !io->isIncoming() && handshake->state == AWAITING_YB) + if (io->socket.is_utp() && !io->isIncoming() && handshake->is_state(State::AwaitingYb)) { // the peer probably doesn't speak µTP. auto const info_hash = io->torrentHash(); - auto const info = handshake->mediator.torrentInfo(info_hash); + auto const info = handshake->mediator_->torrent_info(info_hash); /* Don't mark a peer as non-µTP unless it's really a connect failure. */ if ((errcode == ETIMEDOUT || errcode == ECONNREFUSED) && info) { - handshake->mediator.setUTPFailed(info_hash, io->address()); + handshake->set_utp_failed(info_hash, io->address()); } - if (handshake->mediator.allowsTCP() && handshake->io->reconnect() == 0) + if (handshake->mediator_->allows_tcp() && io->reconnect() == 0) { auto msg = std::array{}; - buildHandshakeMessage(handshake, std::data(msg)); - handshake->haveSentBitTorrentHandshake = true; - setReadState(handshake, AWAITING_HANDSHAKE); - handshake->io->writeBytes(std::data(msg), std::size(msg), false); + handshake->build_handshake_message(io, std::data(msg)); + handshake->have_sent_bittorrent_handshake_ = true; + handshake->set_state(State::AwaitingHandshake); + io->writeBytes(std::data(msg), std::size(msg), false); } } /* if the error happened while we were sending a public key, we might * have encountered a peer that doesn't do encryption... reconnect and * try a plaintext handshake */ - if ((handshake->state == AWAITING_YB || handshake->state == AWAITING_VC) && - handshake->encryption_mode != TR_ENCRYPTION_REQUIRED && handshake->mediator.allowsTCP() && - handshake->io->reconnect() == 0) + if ((handshake->is_state(State::AwaitingYb) || handshake->is_state(State::AwaitingVc)) && + handshake->encryption_mode_ != TR_ENCRYPTION_REQUIRED && handshake->mediator_->allows_tcp() && io->reconnect() == 0) { auto msg = std::array{}; tr_logAddTraceHand(handshake, "handshake failed, trying plaintext..."); - buildHandshakeMessage(handshake, std::data(msg)); - handshake->haveSentBitTorrentHandshake = true; - setReadState(handshake, AWAITING_HANDSHAKE); - handshake->io->writeBytes(std::data(msg), std::size(msg), false); + handshake->build_handshake_message(io, std::data(msg)); + handshake->have_sent_bittorrent_handshake_ = true; + handshake->set_state(State::AwaitingHandshake); + io->writeBytes(std::data(msg), std::size(msg), false); } else { tr_logAddTraceHand( handshake, fmt::format("libevent got an error: what={:d}, errno={:d} ({:s})", what, errcode, tr_strerror(errcode))); - tr_handshakeDone(handshake, false); + handshake->done(false); } } @@ -1098,38 +920,37 @@ static void gotError(tr_peerIo* io, short what, void* vhandshake) *** **/ -tr_handshake* tr_handshakeNew( - tr_handshake_mediator& mediator, - std::shared_ptr io, - tr_encryption_mode encryption_mode, - tr_handshake_done_func done_func, - void* done_func_user_data) +tr_handshake::tr_handshake( + Mediator* mediator, + std::shared_ptr peer_io, + tr_encryption_mode mode_in, + DoneFunc done_func) + : dh_{ mediator->private_key() } + , done_func_{ std::move(done_func) } + , peer_io_{ std::move(peer_io) } + , timeout_timer_{ mediator->timer_maker().create([this]() { fire_done(false); }) } + , mediator_{ mediator } + , encryption_mode_{ mode_in } { - auto* const handshake = new tr_handshake{ mediator, std::move(io), encryption_mode }; - handshake->done_func = done_func; - handshake->done_func_user_data = done_func_user_data; - handshake->timeout_timer = handshake->mediator.timerMaker().create([handshake]() { tr_handshakeAbort(handshake); }); - handshake->timeout_timer->startSingleShot(HandshakeTimeoutSec); + timeout_timer_->startSingleShot(HandshakeTimeoutSec); - handshake->io->setCallbacks(canRead, nullptr, gotError, handshake); + peer_io_->setCallbacks(&tr_handshake::can_read, nullptr, &tr_handshake::on_error, this); - if (handshake->isIncoming()) + if (is_incoming()) { - setReadState(handshake, AWAITING_HANDSHAKE); + set_state(State::AwaitingHandshake); } - else if (encryption_mode != TR_CLEAR_PREFERRED) + else if (encryption_mode_ != TR_CLEAR_PREFERRED) { - sendYa(handshake); + send_ya(peer_io_.get()); } else { auto msg = std::array{}; - buildHandshakeMessage(handshake, std::data(msg)); + build_handshake_message(peer_io_.get(), std::data(msg)); - handshake->haveSentBitTorrentHandshake = true; - setReadState(handshake, AWAITING_HANDSHAKE); - handshake->io->writeBytes(std::data(msg), std::size(msg), false); + have_sent_bittorrent_handshake_ = true; + set_state(State::AwaitingHandshake); + peer_io_->writeBytes(std::data(msg), std::size(msg), false); } - - return handshake; } diff --git a/libtransmission/handshake.h b/libtransmission/handshake.h index 24a2728ae..cd6c595ed 100644 --- a/libtransmission/handshake.h +++ b/libtransmission/handshake.h @@ -9,17 +9,18 @@ #error only libtransmission should #include this header. #endif +#include #include // for size_t -#include +#include #include +#include #include "transmission.h" #include "net.h" // tr_address #include "peer-mse.h" // tr_message_stream_encryption::DH - -/** @addtogroup peers Peers - @{ */ +#include "peer-io.h" +#include "timer.h" namespace libtransmission { @@ -30,64 +31,265 @@ class tr_peerIo; /** @brief opaque struct holding handshake state information. freed when the handshake is completed. */ -struct tr_handshake; -struct tr_handshake_result -{ - struct tr_handshake* handshake; - std::shared_ptr io; - bool readAnythingFromPeer; - bool isConnected; - void* userData; - std::optional peer_id; -}; - -class tr_handshake_mediator +class tr_handshake { public: - struct torrent_info + using DH = tr_message_stream_encryption::DH; + + struct Result { - tr_sha1_digest_t info_hash; - tr_peer_id_t client_peer_id; - tr_torrent_id_t id; - bool is_done; + std::shared_ptr io; + std::optional peer_id; + bool read_anything_from_peer; + bool is_connected; }; - virtual ~tr_handshake_mediator() = default; + using DoneFunc = std::function; - [[nodiscard]] virtual std::optional torrentInfo(tr_sha1_digest_t const& info_hash) const = 0; - - [[nodiscard]] virtual std::optional torrentInfoFromObfuscated(tr_sha1_digest_t const& info_hash) const = 0; - - [[nodiscard]] virtual libtransmission::TimerMaker& timerMaker() = 0; - - [[nodiscard]] virtual bool allowsDHT() const = 0; - - [[nodiscard]] virtual bool allowsTCP() const = 0; - - [[nodiscard]] virtual bool isPeerKnownSeed(tr_torrent_id_t tor_id, tr_address addr) const = 0; - - [[nodiscard]] virtual size_t pad(void* setme, size_t max_bytes) const = 0; - - [[nodiscard]] virtual tr_message_stream_encryption::DH::private_key_bigend_t privateKey() const + class Mediator { - return tr_message_stream_encryption::DH::randomPrivateKey(); + public: + struct TorrentInfo + { + tr_sha1_digest_t info_hash; + tr_peer_id_t client_peer_id; + tr_torrent_id_t id; + bool is_done; + }; + + virtual ~Mediator() = default; + + [[nodiscard]] virtual std::optional torrent_info(tr_sha1_digest_t const& info_hash) const = 0; + [[nodiscard]] virtual std::optional torrent_info_from_obfuscated( + tr_sha1_digest_t const& info_hash) const = 0; + [[nodiscard]] virtual libtransmission::TimerMaker& timer_maker() = 0; + [[nodiscard]] virtual bool allows_dht() const = 0; + [[nodiscard]] virtual bool allows_tcp() const = 0; + [[nodiscard]] virtual bool is_peer_known_seed(tr_torrent_id_t tor_id, tr_address const& addr) const = 0; + [[nodiscard]] virtual size_t pad(void* setme, size_t max_bytes) const = 0; + [[nodiscard]] virtual DH::private_key_bigend_t private_key() const + { + return DH::randomPrivateKey(); + } + + virtual void set_utp_failed(tr_sha1_digest_t const& info_hash, tr_address const&) = 0; + }; + + tr_handshake(Mediator* mediator, std::shared_ptr peer_io, tr_encryption_mode mode_in, DoneFunc done_func); + +private: + enum class State + { + // incoming + AwaitingHandshake, + AwaitingPeerId, + AwaitingYa, + AwaitingPadA, + AwaitingCryptoProvide, + AwaitingPadC, + AwaitingIa, + AwaitingPayloadStream, + + // outgoing + AwaitingYb, + AwaitingVc, + AwaitingCryptoSelect, + AwaitingPadD + }; + + bool build_handshake_message(tr_peerIo* io, uint8_t* buf) const; + + ReadState read_crypto_provide(tr_peerIo* peer_io); + ReadState read_crypto_select(tr_peerIo* peer_io); + ReadState read_handshake(tr_peerIo* peer_io); + ReadState read_ia(tr_peerIo* peer_io); + ReadState read_pad_a(tr_peerIo* peer_io); + ReadState read_pad_c(tr_peerIo* peer_io); + ReadState read_pad_d(tr_peerIo* peer_io); + ReadState read_payload_stream(tr_peerIo* peer_io); + ReadState read_peer_id(tr_peerIo* peer_io); + ReadState read_vc(tr_peerIo* peer_io); + ReadState read_ya(tr_peerIo* peer_io); + ReadState read_yb(tr_peerIo* peer_io); + + void send_ya(tr_peerIo* io); + + enum class ParseResult + { + Ok, + EncryptionWrong, + BadTorrent, + PeerIsSelf, + }; + + ParseResult parse_handshake(tr_peerIo* peer_io); + + static ReadState can_read(tr_peerIo* peer_io, void* vhandshake, size_t* piece); + static void on_error(tr_peerIo* io, short what, void* vhandshake); + + void set_peer_id(tr_peer_id_t const& id) noexcept + { + peer_id_ = id; } - virtual void setUTPFailed(tr_sha1_digest_t const& info_hash, tr_address) = 0; + void set_have_read_anything_from_peer(bool val) noexcept + { + have_read_anything_from_peer_ = val; + } + + ReadState done(bool is_connected) + { + peer_io_->clearCallbacks(); + return fire_done(is_connected) ? READ_LATER : READ_ERR; + } + + [[nodiscard]] auto is_incoming() const noexcept + { + return peer_io_->isIncoming(); + } + + [[nodiscard]] auto display_name() const + { + return peer_io_->display_name(); + } + + void set_utp_failed(tr_sha1_digest_t const& info_hash, tr_address const& addr) + { + mediator_->set_utp_failed(info_hash, addr); + } + + [[nodiscard]] constexpr auto state() const noexcept + { + return state_; + } + + [[nodiscard]] constexpr auto is_state(State state) const noexcept + { + return state_ == state; + } + + constexpr void set_state(State state) + { + state_ = state; + } + + [[nodiscard]] constexpr std::string_view state_string() const + { + return state_string(state_); + } + + [[nodiscard]] constexpr uint32_t crypto_provide() const + { + uint32_t provide = 0; + + switch (encryption_mode_) + { + case TR_ENCRYPTION_REQUIRED: + case TR_ENCRYPTION_PREFERRED: + provide |= CryptoProvideCrypto; + break; + + case TR_CLEAR_PREFERRED: + provide |= CryptoProvideCrypto | CryptoProvidePlaintext; + break; + } + + return provide; + } + + bool fire_done(bool is_connected) + { + if (!done_func_) + { + return false; + } + + auto cb = DoneFunc{}; + std::swap(cb, done_func_); + + auto peer_io = std::shared_ptr{}; + std::swap(peer_io, peer_io_); + + bool const success = (cb)(Result{ std::move(peer_io), peer_id_, have_read_anything_from_peer_, is_connected }); + return success; + } + + static auto constexpr HandshakeTimeoutSec = std::chrono::seconds{ 30 }; + + [[nodiscard]] static constexpr std::string_view state_string(State state) + { + using State = tr_handshake::State; + + switch (state) + { + case State::AwaitingHandshake: + return "awaiting handshake"; + case State::AwaitingPeerId: + return "awaiting peer id"; + case State::AwaitingYa: + return "awaiting ya"; + case State::AwaitingPadA: + return "awaiting pad a"; + case State::AwaitingCryptoProvide: + return "awaiting crypto provide"; + case State::AwaitingPadC: + return "awaiting pad c"; + case State::AwaitingIa: + return "awaiting ia"; + case State::AwaitingPayloadStream: + return "awaiting payload stream"; + + // outgoing + case State::AwaitingYb: + return "awaiting yb"; + case State::AwaitingVc: + return "awaiting vc"; + case State::AwaitingCryptoSelect: + return "awaiting crypto select"; + case State::AwaitingPadD: + return "awaiting pad d"; + } + } + + template + void send_public_key_and_pad(tr_peerIo* io) + { + auto const public_key = dh_.publicKey(); + auto outbuf = std::array{}; + auto const data = std::data(outbuf); + auto walk = data; + walk = std::copy(std::begin(public_key), std::end(public_key), walk); + walk += mediator_->pad(walk, PadMax); + io->writeBytes(data, walk - data, false); + } + + static auto constexpr CryptoProvidePlaintext = int{ 1 }; + static auto constexpr CryptoProvideCrypto = int{ 2 }; + + DH dh_ = {}; + + DoneFunc done_func_; + + std::optional peer_id_; + + std::shared_ptr peer_io_; + + std::unique_ptr timeout_timer_; + + Mediator* mediator_ = nullptr; + + State state_ = State::AwaitingHandshake; + + tr_encryption_mode encryption_mode_; + + uint32_t crypto_select_ = {}; + uint32_t crypto_provide_ = {}; + uint16_t pad_c_len_ = {}; + uint16_t pad_d_len_ = {}; + uint16_t ia_len_ = {}; + + bool have_read_anything_from_peer_ = false; + + bool have_sent_bittorrent_handshake_ = false; }; - -/* returns true on success, false on error */ -using tr_handshake_done_func = bool (*)(tr_handshake_result const& result); - -/** @brief create a new handshake */ -tr_handshake* tr_handshakeNew( - tr_handshake_mediator& mediator, - std::shared_ptr io, - tr_encryption_mode encryption_mode, - tr_handshake_done_func done_func, - void* done_func_user_data); - -void tr_handshakeAbort(tr_handshake* handshake); - -/** @} */ diff --git a/libtransmission/peer-mgr.cc b/libtransmission/peer-mgr.cc index 4b0b057b2..01f4db272 100644 --- a/libtransmission/peer-mgr.cc +++ b/libtransmission/peer-mgr.cc @@ -12,6 +12,7 @@ #include #include // time_t #include +#include #include // std::back_inserter #include #include // std::accumulate @@ -66,17 +67,17 @@ static auto constexpr CancelHistorySec = int{ 60 }; static bool tr_peerMgrPeerIsSeed(tr_torrent const* tor, tr_address const& addr); -class tr_handshake_mediator_impl final : public tr_handshake_mediator +class HandshakeMediator final : public tr_handshake::Mediator { private: - [[nodiscard]] static std::optional torrentInfo(tr_torrent* tor) + [[nodiscard]] static std::optional torrent_info(tr_torrent* tor) { if (tor == nullptr) { return {}; } - auto info = torrent_info{}; + auto info = TorrentInfo{}; info.info_hash = tor->infoHash(); info.client_peer_id = tr_torrentGetPeerId(tor); info.id = tor->id(); @@ -85,33 +86,33 @@ private: } public: - explicit tr_handshake_mediator_impl(tr_session& session) noexcept + explicit HandshakeMediator(tr_session& session) noexcept : session_{ session } { } - [[nodiscard]] std::optional torrentInfo(tr_sha1_digest_t const& info_hash) const override + [[nodiscard]] std::optional torrent_info(tr_sha1_digest_t const& info_hash) const override { - return torrentInfo(session_.torrents().get(info_hash)); + return torrent_info(session_.torrents().get(info_hash)); } - [[nodiscard]] std::optional torrentInfoFromObfuscated( + [[nodiscard]] std::optional torrent_info_from_obfuscated( tr_sha1_digest_t const& obfuscated_info_hash) const override { - return torrentInfo(tr_torrentFindFromObfuscatedHash(&session_, obfuscated_info_hash)); + return torrent_info(tr_torrentFindFromObfuscatedHash(&session_, obfuscated_info_hash)); } - [[nodiscard]] bool allowsDHT() const override + [[nodiscard]] bool allows_dht() const override { return session_.allowsDHT(); } - [[nodiscard]] bool allowsTCP() const override + [[nodiscard]] bool allows_tcp() const override { return session_.allowsTCP(); } - void setUTPFailed(tr_sha1_digest_t const& info_hash, tr_address addr) override + void set_utp_failed(tr_sha1_digest_t const& info_hash, tr_address const& addr) override { if (auto* const tor = session_.torrents().get(info_hash); tor != nullptr) { @@ -119,13 +120,13 @@ public: } } - [[nodiscard]] bool isPeerKnownSeed(tr_torrent_id_t tor_id, tr_address addr) const override + [[nodiscard]] bool is_peer_known_seed(tr_torrent_id_t tor_id, tr_address const& addr) const override { auto const* const tor = session_.torrents().get(tor_id); return tor != nullptr && tr_peerMgrPeerIsSeed(tor, addr); } - [[nodiscard]] libtransmission::TimerMaker& timerMaker() override + [[nodiscard]] libtransmission::TimerMaker& timer_maker() override { return session_.timerMaker(); } @@ -296,58 +297,7 @@ private: static auto constexpr MinimumReconnectIntervalSecs = int{ 5 }; }; -// a container for keeping track of tr_handshakes -class Handshakes -{ -public: - void add(tr_address const& address, tr_handshake* handshake) - { - TR_ASSERT(!contains(address)); - - handshakes_.emplace_back(address, handshake); - } - - [[nodiscard]] bool contains(tr_address const& address) const noexcept - { - return std::any_of( - std::begin(handshakes_), - std::end(handshakes_), - [&address](auto const& pair) { return pair.first == address; }); - } - - void erase(tr_address const& address) - { - for (auto iter = std::begin(handshakes_), end = std::end(handshakes_); iter != end; ++iter) - { - if (iter->first == address) - { - handshakes_.erase(iter); - return; - } - } - } - - [[nodiscard]] auto empty() const noexcept - { - return std::empty(handshakes_); - } - - void abortAll() - { - // make a tmp copy so that calls to tr_handshakeAbort() won't - // be able to invalidate its loop iteration - auto tmp = handshakes_; - for (auto& [addr, handshake] : tmp) - { - tr_handshakeAbort(handshake); - } - - handshakes_ = {}; - } - -private: - std::vector> handshakes_; -}; +using Handshakes = std::map; #define tr_logAddDebugSwarm(swarm, msg) tr_logAddDebugTor((swarm)->tor, msg) #define tr_logAddTraceSwarm(swarm, msg) tr_logAddTraceTor((swarm)->tor, msg) @@ -414,7 +364,7 @@ public: is_running = false; removeAllPeers(); - outgoing_handshakes.abortAll(); + outgoing_handshakes.clear(); } void removePeer(tr_peer* peer) @@ -586,7 +536,7 @@ struct tr_peerMgr ~tr_peerMgr() { auto const lock = unique_lock(); - incoming_handshakes.abortAll(); + incoming_handshakes.clear(); } void rechokeSoon() noexcept @@ -603,7 +553,7 @@ struct tr_peerMgr tr_session* const session; Handshakes incoming_handshakes; - tr_handshake_mediator_impl handshake_mediator_; + HandshakeMediator handshake_mediator_; private: void rechokePulseMarshall() @@ -684,8 +634,8 @@ static struct peer_atom* getExistingAtom(tr_swarm const* cswarm, tr_address cons static bool peerIsInUse(tr_swarm const* swarm, struct peer_atom const* atom) { - return atom->is_connected || swarm->outgoing_handshakes.contains(atom->addr) || - swarm->manager->incoming_handshakes.contains(atom->addr); + return atom->is_connected || swarm->outgoing_handshakes.count(atom->addr) != 0U || + swarm->manager->incoming_handshakes.count(atom->addr) != 0U; } static void swarmFree(tr_swarm* s) @@ -1133,13 +1083,12 @@ static void createBitTorrentPeer(tr_torrent* tor, std::shared_ptr io, } /* FIXME: this is kind of a mess. */ -static bool on_handshake_done(tr_handshake_result const& result) +static bool on_handshake_done(tr_peerMgr* manager, tr_handshake::Result const& result) { TR_ASSERT(result.io != nullptr); - bool const ok = result.isConnected; + bool const ok = result.is_connected; bool success = false; - auto* manager = static_cast(result.userData); tr_swarm* const s = getExistingSwarm(manager, result.io->torrentHash()); @@ -1166,7 +1115,7 @@ static bool on_handshake_done(tr_handshake_result const& result) { ++atom->num_fails; - if (!result.readAnythingFromPeer) + if (!result.read_anything_from_peer) { tr_logAddTraceSwarm( s, @@ -1244,21 +1193,19 @@ void tr_peerMgrAddIncoming(tr_peerMgr* manager, tr_peer_socket&& socket) tr_logAddTrace(fmt::format("Banned IP address '{}' tried to connect to us", socket.display_name())); socket.close(session); } - else if (manager->incoming_handshakes.contains(socket.address())) + else if (manager->incoming_handshakes.count(socket.address()) != 0U) { socket.close(session); } else /* we don't have a connection to them yet... */ { auto address = socket.address(); - manager->incoming_handshakes.add( + manager->incoming_handshakes.try_emplace( address, - tr_handshakeNew( - manager->handshake_mediator_, - tr_peerIo::newIncoming(session, &session->top_bandwidth_, std::move(socket)), - session->encryptionMode(), - on_handshake_done, - manager)); + &manager->handshake_mediator_, + tr_peerIo::newIncoming(session, &session->top_bandwidth_, std::move(socket)), + session->encryptionMode(), + [manager](tr_handshake::Result const& result) { return on_handshake_done(manager, result); }); } } @@ -2813,7 +2760,7 @@ void initiateConnection(tr_peerMgr* mgr, tr_swarm* s, peer_atom& atom) s, fmt::format("Starting an OUTGOING {} connection with {}", utp ? " µTP" : "TCP", atom.display_name())); - auto io = tr_peerIo::newOutgoing( + auto peer_io = tr_peerIo::newOutgoing( mgr->session, &mgr->session->top_bandwidth_, atom.addr, @@ -2822,7 +2769,7 @@ void initiateConnection(tr_peerMgr* mgr, tr_swarm* s, peer_atom& atom) s->tor->completeness == TR_SEED, utp); - if (io == nullptr) + if (!peer_io) { tr_logAddTraceSwarm(s, fmt::format("peerIo not created; marking peer {} as unreachable", atom.display_name())); atom.flags2 |= MyflagUnreachable; @@ -2830,13 +2777,12 @@ void initiateConnection(tr_peerMgr* mgr, tr_swarm* s, peer_atom& atom) } else { - auto* const handshake = tr_handshakeNew( - mgr->handshake_mediator_, - std::move(io), + s->outgoing_handshakes.try_emplace( + atom.addr, + &mgr->handshake_mediator_, + peer_io, mgr->session->encryptionMode(), - on_handshake_done, - mgr); - s->outgoing_handshakes.add(atom.addr, handshake); + [mgr](tr_handshake::Result const& result) { return on_handshake_done(mgr, result); }); } atom.lastConnectionAttemptAt = now; diff --git a/tests/libtransmission/handshake-test.cc b/tests/libtransmission/handshake-test.cc index b7974e827..f9d9c882c 100644 --- a/tests/libtransmission/handshake-test.cc +++ b/tests/libtransmission/handshake-test.cc @@ -35,7 +35,7 @@ auto constexpr MaxWaitMsec = int{ 5000 }; class HandshakeTest : public SessionTest { public: - class MediatorMock final : public tr_handshake_mediator + class MediatorMock final : public tr_handshake::Mediator { public: explicit MediatorMock(tr_session* session) @@ -43,7 +43,7 @@ public: { } - [[nodiscard]] std::optional torrentInfo(tr_sha1_digest_t const& info_hash) const override + [[nodiscard]] std::optional torrent_info(tr_sha1_digest_t const& info_hash) const override { if (auto const iter = torrents.find(info_hash); iter != std::end(torrents)) { @@ -53,7 +53,7 @@ public: return {}; } - [[nodiscard]] std::optional torrentInfoFromObfuscated(tr_sha1_digest_t const& obfuscated) const override + [[nodiscard]] std::optional torrent_info_from_obfuscated(tr_sha1_digest_t const& obfuscated) const override { for (auto const& [info_hash, info] : torrents) { @@ -66,22 +66,22 @@ public: return {}; } - [[nodiscard]] libtransmission::TimerMaker& timerMaker() override + [[nodiscard]] libtransmission::TimerMaker& timer_maker() override { return session_->timerMaker(); } - [[nodiscard]] bool allowsDHT() const override + [[nodiscard]] bool allows_dht() const override { return false; } - [[nodiscard]] bool allowsTCP() const override + [[nodiscard]] bool allows_tcp() const override { return true; } - [[nodiscard]] bool isPeerKnownSeed(tr_torrent_id_t /*tor_id*/, tr_address /*addr*/) const override + [[nodiscard]] bool is_peer_known_seed(tr_torrent_id_t /*tor_id*/, tr_address const& /*addr*/) const override { return false; } @@ -94,12 +94,12 @@ public: return len; } - [[nodiscard]] tr_message_stream_encryption::DH::private_key_bigend_t privateKey() const override + [[nodiscard]] tr_message_stream_encryption::DH::private_key_bigend_t private_key() const override { return private_key_; } - void setUTPFailed(tr_sha1_digest_t const& /*info_hash*/, tr_address /*addr*/) override + void set_utp_failed(tr_sha1_digest_t const& /*info_hash*/, tr_address const& /*addr*/) override { } @@ -111,7 +111,7 @@ public: } tr_session* const session_; - std::map torrents; + std::map torrents; tr_message_stream_encryption::DH::private_key_bigend_t private_key_ = {}; }; @@ -145,11 +145,11 @@ public: tr_address const DefaultPeerAddr = *tr_address::from_string("127.0.0.1"sv); tr_port const DefaultPeerPort = tr_port::fromHost(8080); - tr_handshake_mediator::torrent_info const TorrentWeAreSeeding{ tr_sha1::digest("abcde"sv), + tr_handshake::Mediator::TorrentInfo const TorrentWeAreSeeding{ tr_sha1::digest("abcde"sv), tr_peerIdInit(), tr_torrent_id_t{ 100 }, true /*is_done*/ }; - tr_handshake_mediator::torrent_info const UbuntuTorrent{ *tr_sha1_from_string("2c6b6858d61da9543d4231a71db4b1c9264b0685"sv), + tr_handshake::Mediator::TorrentInfo const UbuntuTorrent{ *tr_sha1_from_string("2c6b6858d61da9543d4231a71db4b1c9264b0685"sv), tr_peerIdInit(), tr_torrent_id_t{ 101 }, false /*is_done*/ }; @@ -158,25 +158,27 @@ public: { auto sockpair = std::array{ -1, -1 }; EXPECT_EQ(0, evutil_socketpair(LOCAL_SOCKETPAIR_AF, SOCK_STREAM, 0, std::data(sockpair))) << tr_strerror(errno); - auto io = tr_peerIo::newIncoming( - session, - &session->top_bandwidth_, - tr_peer_socket(session, DefaultPeerAddr, DefaultPeerPort, sockpair[0])); - return std::make_pair(io, sockpair[1]); + return std::make_pair( + tr_peerIo::newIncoming( + session, + &session->top_bandwidth_, + tr_peer_socket(session, DefaultPeerAddr, DefaultPeerPort, sockpair[0])), + sockpair[1]); } auto createOutgoingIo(tr_session* session, tr_sha1_digest_t const& info_hash) { auto sockpair = std::array{ -1, -1 }; EXPECT_EQ(0, evutil_socketpair(LOCAL_SOCKETPAIR_AF, SOCK_STREAM, 0, std::data(sockpair))) << tr_strerror(errno); - auto io = tr_peerIo::create( - session, - &session->top_bandwidth_, - &info_hash, - false /*is_incoming*/, - false /*is_seed*/, - tr_peer_socket(session, DefaultPeerAddr, DefaultPeerPort, sockpair[0])); - return std::make_pair(io, sockpair[1]); + return std::make_pair( + tr_peerIo::create( + session, + &session->top_bandwidth_, + &info_hash, + false /*is_incoming*/, + false /*is_seed*/, + tr_peer_socket(session, DefaultPeerAddr, DefaultPeerPort, sockpair[0])), + sockpair[1]); } static constexpr auto makePeerId(std::string_view sv) @@ -198,22 +200,22 @@ public: } static auto runHandshake( - tr_handshake_mediator& mediator, - std::shared_ptr io, + tr_handshake::Mediator* mediator, + std::shared_ptr const& peer_io, tr_encryption_mode encryption_mode = TR_CLEAR_PREFERRED) { - auto result = std::optional{}; + auto result = std::optional{}; - static auto const DoneCallback = [](auto const& resin) - { - *static_cast*>(resin.userData) = resin; - return true; - }; - - tr_handshakeNew(mediator, std::move(io), encryption_mode, DoneCallback, &result); + auto handshake = tr_handshake{ mediator, + peer_io, + encryption_mode, + [&result](auto const& resin) + { + result = resin; + return true; + } }; waitFor([&result]() { return result.has_value(); }, MaxWaitMsec); - return result; } }; @@ -239,12 +241,12 @@ TEST_F(HandshakeTest, incomingPlaintext) sendToClient(sock, TorrentWeAreSeeding.info_hash); sendToClient(sock, peer_id); - auto const res = runHandshake(mediator, io); + auto const res = runHandshake(&mediator, io); // check the results EXPECT_TRUE(res); - EXPECT_TRUE(res->isConnected); - EXPECT_TRUE(res->readAnythingFromPeer); + EXPECT_TRUE(res->is_connected); + EXPECT_TRUE(res->read_anything_from_peer); EXPECT_EQ(io, res->io); EXPECT_TRUE(res->peer_id); EXPECT_EQ(peer_id, res->peer_id); @@ -266,12 +268,12 @@ TEST_F(HandshakeTest, incomingPlaintextUnknownInfoHash) sendToClient(sock, tr_sha1::digest("some other torrent unknown to us"sv)); sendToClient(sock, makeRandomPeerId()); - auto const res = runHandshake(mediator, io); + auto const res = runHandshake(&mediator, io); // check the results EXPECT_TRUE(res); - EXPECT_FALSE(res->isConnected); - EXPECT_TRUE(res->readAnythingFromPeer); + EXPECT_FALSE(res->is_connected); + EXPECT_TRUE(res->read_anything_from_peer); EXPECT_EQ(io, res->io); EXPECT_FALSE(res->peer_id); EXPECT_EQ(tr_sha1_digest_t{}, io->torrentHash()); @@ -291,12 +293,12 @@ TEST_F(HandshakeTest, outgoingPlaintext) sendToClient(sock, UbuntuTorrent.info_hash); sendToClient(sock, peer_id); - auto const res = runHandshake(mediator, io); + auto const res = runHandshake(&mediator, io); // check the results EXPECT_TRUE(res); - EXPECT_TRUE(res->isConnected); - EXPECT_TRUE(res->readAnythingFromPeer); + EXPECT_TRUE(res->is_connected); + EXPECT_TRUE(res->read_anything_from_peer); EXPECT_EQ(io, res->io); EXPECT_TRUE(res->peer_id); EXPECT_EQ(peer_id, res->peer_id); @@ -329,12 +331,12 @@ TEST_F(HandshakeTest, incomingEncrypted) "VGwrTPstEPu3V5lmzjtMGVLaL5EErlpJ93Xrz+ea6EIQEUZA+D4jKaV/to9NVi" "04/1W1A2PHgg+I9puac/i9BsFPcjdQeoVtU73lNCbTDQgTieyjDWmwo="sv); - auto const res = runHandshake(mediator, io); + auto const res = runHandshake(&mediator, io); // check the results EXPECT_TRUE(res); - EXPECT_TRUE(res->isConnected); - EXPECT_TRUE(res->readAnythingFromPeer); + EXPECT_TRUE(res->is_connected); + EXPECT_TRUE(res->read_anything_from_peer); EXPECT_EQ(io, res->io); EXPECT_TRUE(res->peer_id); EXPECT_EQ(ExpectedPeerId, res->peer_id); @@ -366,12 +368,12 @@ TEST_F(HandshakeTest, incomingEncryptedUnknownInfoHash) "VGwrTPstEPu3V5lmzjtMGVLaL5EErlpJ93Xrz+ea6EIQEUZA+D4jKaV/to9NVi" "04/1W1A2PHgg+I9puac/i9BsFPcjdQeoVtU73lNCbTDQgTieyjDWmwo="sv); - auto const res = runHandshake(mediator, io); + auto const res = runHandshake(&mediator, io); // check the results EXPECT_TRUE(res); - EXPECT_FALSE(res->isConnected); - EXPECT_TRUE(res->readAnythingFromPeer); + EXPECT_FALSE(res->is_connected); + EXPECT_TRUE(res->read_anything_from_peer); EXPECT_EQ(tr_sha1_digest_t{}, io->torrentHash()); evutil_closesocket(sock); @@ -405,12 +407,12 @@ TEST_F(HandshakeTest, outgoingEncrypted) "3+o/RdiKQJAsGxMIU08scBc5VOmrAmjeYrLNpFnpXVuavH5if7490zMCu3DEn" "G9hpbYbiX95T+EUcRbM6pSCvr3Twq1Q="sv); - auto const res = runHandshake(mediator, io, TR_ENCRYPTION_PREFERRED); + auto const res = runHandshake(&mediator, io, TR_ENCRYPTION_PREFERRED); // check the results EXPECT_TRUE(res); - EXPECT_TRUE(res->isConnected); - EXPECT_TRUE(res->readAnythingFromPeer); + EXPECT_TRUE(res->is_connected); + EXPECT_TRUE(res->read_anything_from_peer); EXPECT_EQ(io, res->io); EXPECT_TRUE(res->peer_id); EXPECT_EQ(ExpectedPeerId, res->peer_id);