// This file Copyright © Mnemosyne LLC. // It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only), // or any future license endorsed by Mnemosyne LLC. // License text can be found in the licenses/ folder. #include #include #include // ECONNREFUSED, ETIMEDOUT #include #include #include #include #include #include #include "libtransmission/transmission.h" #include "libtransmission/bitfield.h" #include "libtransmission/clients.h" #include "libtransmission/crypto-utils.h" #include "libtransmission/error.h" #include "libtransmission/handshake.h" #include "libtransmission/log.h" #include "libtransmission/peer-io.h" #include "libtransmission/peer-mse.h" // tr_message_stream_encryption::DH #include "libtransmission/timer.h" #include "libtransmission/tr-assert.h" #include "libtransmission/tr-buffer.h" #include "libtransmission/tr-macros.h" // tr_peer_id_t #define tr_logAddTraceHand(handshake, msg) \ tr_logAddTrace(msg, fmt::format("handshake {}", (handshake)->peer_io_->display_name())) using namespace std::literals; using key_bigend_t = tr_message_stream_encryption::DH::key_bigend_t; // --- Outgoing Connections // 1 A->B: our public key (Ya) and some padding (PadA) void tr_handshake::send_ya(tr_peerIo* io) { tr_logAddTraceHand(this, "sending MSE handshake (Ya)"); send_public_key_and_pad(io); set_state(tr_handshake::State::AwaitingYb); } ReadState tr_handshake::read_yb(tr_peerIo* peer_io) { if (peer_io->read_buffer_size() < std::size(HandshakeName)) { return READ_LATER; } // Jump to plain handshake if (peer_io->read_buffer_starts_with(HandshakeName)) { tr_logAddTraceHand(this, "in read_yb... got a plain incoming handshake"); set_state(tr_handshake::State::AwaitingHandshake); return READ_NOW; } auto peer_public_key = key_bigend_t{}; tr_logAddTraceHand( this, fmt::format("in read_yb... need {}, have {}", std::size(peer_public_key), peer_io->read_buffer_size())); if (peer_io->read_buffer_size() < std::size(peer_public_key)) { return READ_LATER; } have_read_anything_from_peer_ = true; // get the peer's public key peer_io->read_bytes(std::data(peer_public_key), std::size(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) */ static auto constexpr BufSize = std::tuple_size_v * 2 + std::size(VC) + sizeof(crypto_provide_) + sizeof(pad_c_len_) + sizeof(ia_len_) + HandshakeSize; auto outbuf = libtransmission::StackBuffer{}; /* HASH('req1', S) */ outbuf.add(tr_sha1::digest("req1"sv, dh_.secret())); auto const& info_hash = peer_io->torrent_hash(); TR_ASSERT_MSG(info_hash != tr_sha1_digest_t{}, "readYb requires an info_hash"); /* 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, dh_.secret()); auto [x_or, n_x_or] = outbuf.reserve_space(std::tuple_size_v); for (size_t i = 0; i < n_x_or; ++i) { x_or[i] = req2[i] ^ req3[i]; } outbuf.commit_space(n_x_or); } /* ENCRYPT(VC, crypto_provide, len(PadC), PadC * PadC is reserved for future extensions to the handshake... * standard practice at this time is for it to be zero-length */ crypto_provide_ = crypto_provide(); peer_io->write(outbuf, false); peer_io->encrypt_init(peer_io->is_incoming(), dh_, info_hash); outbuf.add(VC); outbuf.add_uint32(crypto_provide_); outbuf.add_uint16(0); /* ENCRYPT len(IA)), ENCRYPT(IA) */ outbuf.add_uint16(HandshakeSize); if (build_handshake_message(peer_io, outbuf)) { have_sent_bittorrent_handshake_ = true; } else { return done(false); } /* send it */ 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)" ReadState tr_handshake::read_vc(tr_peerIo* peer_io) { auto const info_hash = peer_io->torrent_hash(); TR_ASSERT_MSG(info_hash != tr_sha1_digest_t{}, "read_vc requires an info_hash"); // We need to find the end of PadB by looking for `ENCRYPT(VC)`, // so calculate and cache the value of `ENCRYPT(VC)`. if (!encrypted_vc_) { auto filter = tr_message_stream_encryption::Filter{}; filter.encrypt_init(true, dh_, info_hash); encrypted_vc_.emplace(); filter.encrypt(std::data(VC), std::size(VC), std::data(*encrypted_vc_)); } for (; pad_b_recv_len_ <= PadbMaxlen; ++pad_b_recv_len_) { static auto constexpr Needlen = std::size(VC); if (peer_io->read_buffer_size() < Needlen) { tr_logAddTraceHand( this, fmt::format("in read_vc... need {}, read {}, have {}", Needlen, pad_b_recv_len_, peer_io->read_buffer_size())); return READ_LATER; } if (peer_io->read_buffer_starts_with(*encrypted_vc_)) { tr_logAddTraceHand(this, "found ENCRYPT(VC)!"); // We already know it's a match; now we just need to // consume it from the read buffer. peer_io->decrypt_init(peer_io->is_incoming(), dh_, info_hash); peer_io->read_buffer_discard(Needlen); set_state(tr_handshake::State::AwaitingCryptoSelect); return READ_NOW; } peer_io->read_buffer_discard(1U); } tr_logAddTraceHand(this, "couldn't find ENCRYPT(VC)"); return done(false); } ReadState tr_handshake::read_crypto_select(tr_peerIo* peer_io) { if (static auto constexpr NeedLen = sizeof(crypto_select_) + sizeof(pad_d_len_); peer_io->read_buffer_size() < NeedLen) { return READ_LATER; } peer_io->read_uint32(&crypto_select_); tr_logAddTraceHand(this, fmt::format("crypto select is {}", crypto_select_)); if ((crypto_select_ & crypto_provide_) == 0U) { tr_logAddTraceHand(this, "peer selected an encryption option we didn't offer"); return done(false); } peer_io->read_uint16(&pad_d_len_); tr_logAddTraceHand(this, fmt::format("len(PadD) is {}", pad_d_len_)); if (pad_d_len_ > PaddMaxlen) { tr_logAddTraceHand(this, "MSE handshake: len(PadD) is too long"); return done(false); } set_state(tr_handshake::State::AwaitingPadD); return READ_NOW; } ReadState tr_handshake::read_pad_d(tr_peerIo* peer_io) { tr_logAddTraceHand(this, fmt::format("PadD: need {}, got {}", pad_d_len_, peer_io->read_buffer_size())); if (peer_io->read_buffer_size() < pad_d_len_) { return READ_LATER; } peer_io->read_buffer_discard(pad_d_len_); /* maybe de-encrypt our connection */ if (crypto_select_ == CryptoProvidePlaintext) { peer_io->encrypt_disable(); peer_io->decrypt_disable(); } set_state(tr_handshake::State::AwaitingHandshake); return READ_NOW; } // --- Incoming and Outgoing Connections ReadState tr_handshake::read_handshake(tr_peerIo* peer_io) { static auto constexpr Needlen = IncomingHandshakeLen; tr_logAddTraceHand(this, fmt::format("read_handshake: need {}, got {}", Needlen, peer_io->read_buffer_size())); if (peer_io->read_buffer_size() < Needlen) { return READ_LATER; } have_read_anything_from_peer_ = true; if (ia_len_ > 0U) { // do nothing, the check below won't work correctly } else if (peer_io->read_buffer_starts_with(HandshakeName)) // unencrypted { if (encryption_mode_ == TR_ENCRYPTION_REQUIRED) { tr_logAddTraceHand(this, "peer is unencrypted, and we're disallowing that"); return done(false); } if (crypto_select_ == CryptoProvideCrypto) { tr_logAddTraceHand(this, "peer is unencrypted, and that does not agree with our handshake"); return done(false); } } else if (crypto_select_ == CryptoProvidePlaintext) // encrypted { tr_logAddTraceHand(this, "peer is encrypted, and that does not agree with our handshake"); return done(false); } auto name = decltype(HandshakeName){}; peer_io->read_bytes(std::data(name), std::size(name)); if (name != HandshakeName) { tr_logAddTraceHand(this, "handshake prefix not correct"); return done(false); } // reserved bytes / flags auto reserved = std::array{}; auto flags = tr_bitfield{ HandshakeFlagsBits }; peer_io->read_bytes(std::data(reserved), std::size(reserved)); flags.set_raw(std::data(reserved), std::size(reserved)); peer_io->set_supports_dht(flags.test(DhtFlag)); peer_io->set_supports_ltep(flags.test(LtepFlag)); peer_io->set_supports_fext(flags.test(FextFlag)); /* torrent hash */ auto hash = tr_sha1_digest_t{}; peer_io->read_bytes(std::data(hash), std::size(hash)); if (is_incoming() && peer_io->torrent_hash() == tr_sha1_digest_t{}) // incoming plain handshake { if (!mediator_->torrent(hash)) { tr_logAddTraceHand(this, "peer is trying to connect to us for a torrent we don't have."); return done(false); } peer_io->set_torrent_hash(hash); } else // outgoing, or incoming MSE handshake { if (peer_io->torrent_hash() != hash) { tr_logAddTraceHand(this, "peer returned the wrong hash. wtf?"); return done(false); } } // If it's an incoming message, we need to send a response handshake if (!have_sent_bittorrent_handshake_) { tr_logAddTraceHand(this, "sending handshake in reply"); if (!send_handshake(peer_io)) { return done(false); } } set_state(State::AwaitingPeerId); return READ_NOW; } ReadState tr_handshake::read_peer_id(tr_peerIo* peer_io) { // read the peer_id auto peer_id = tr_peer_id_t{}; static auto constexpr Needlen = std::size(peer_id); tr_logAddTraceHand(this, fmt::format("read_peer_id: need {}, got {}", Needlen, peer_io->read_buffer_size())); if (peer_io->read_buffer_size() < Needlen) { return READ_LATER; } peer_io->read_bytes(std::data(peer_id), Needlen); set_peer_id(peer_id); auto client = std::array{}; tr_clientForId(std::data(client), std::size(client), peer_id); 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_->torrent_hash(); auto const info = mediator_->torrent(info_hash); auto const connected_to_self = info && info->client_peer_id == peer_id; return done(!connected_to_self); } // --- Incoming Connections ReadState tr_handshake::read_ya(tr_peerIo* peer_io) { if (peer_io->read_buffer_size() < std::size(HandshakeName)) { return READ_LATER; } // Jump to plain handshake if (peer_io->read_buffer_starts_with(HandshakeName)) { tr_logAddTraceHand(this, "in read_ya... got a plain incoming handshake"); set_state(tr_handshake::State::AwaitingHandshake); return READ_NOW; } auto peer_public_key = key_bigend_t{}; tr_logAddTraceHand( this, fmt::format("in read_ya... need {}, have {}", std::size(peer_public_key), peer_io->read_buffer_size())); if (peer_io->read_buffer_size() < std::size(peer_public_key)) { return READ_LATER; } have_read_anything_from_peer_ = true; /* read the incoming peer's public key */ peer_io->read_bytes(std::data(peer_public_key), std::size(peer_public_key)); dh_.setPeerPublicKey(peer_public_key); // send our public key to the peer tr_logAddTraceHand(this, "sending B->A: Diffie Hellman Yb, PadB"); send_public_key_and_pad(peer_io); set_state(State::AwaitingPadA); return READ_NOW; } 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, dh_.secret()); for (; pad_a_recv_len_ <= PadaMaxlen; ++pad_a_recv_len_) { static auto constexpr Needlen = std::size(needle); if (peer_io->read_buffer_size() < Needlen) { tr_logAddTraceHand( this, fmt::format( "in read_pad_a... need {}, read {}, have {}", Needlen, pad_a_recv_len_, peer_io->read_buffer_size())); return READ_LATER; } if (peer_io->read_buffer_starts_with(needle)) { tr_logAddTraceHand(this, "found HASH('req1', S)!"); peer_io->read_buffer_discard(Needlen); set_state(State::AwaitingCryptoProvide); return READ_NOW; } peer_io->read_buffer_discard(1U); } tr_logAddTraceHand(this, "couldn't find HASH('req1', S)"); return done(false); } ReadState tr_handshake::read_crypto_provide(tr_peerIo* peer_io) { /* HASH('req2', SKEY) xor HASH('req3', S), ENCRYPT(VC, crypto_provide, len(PadC)) */ auto obfuscated_hash = tr_sha1_digest_t{}; static auto constexpr Needlen = std::size(obfuscated_hash) + /* HASH('req2', SKEY) xor HASH('req3', S) */ std::size(VC) + sizeof(crypto_provide_) + sizeof(pad_c_len_); if (peer_io->read_buffer_size() < Needlen) { return READ_LATER; } /* 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(this, "reading obfuscated torrent hash..."); auto x_or = tr_sha1_digest_t{}; peer_io->read_bytes(std::data(x_or), std::size(x_or)); auto const req3 = tr_sha1::digest("req3"sv, dh_.secret()); for (size_t i = 0; i < std::size(obfuscated_hash); ++i) { obfuscated_hash[i] = x_or[i] ^ req3[i]; } if (auto const info = mediator_->torrent_from_obfuscated(obfuscated_hash); info) { tr_logAddTraceHand(this, fmt::format("got INCOMING connection's MSE handshake for torrent [{}]", info->id)); peer_io->set_torrent_hash(info->info_hash); } else { 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->torrent_hash(); TR_ASSERT_MSG(info_hash != tr_sha1_digest_t{}, "read_crypto_provide requires an info_hash"); peer_io->decrypt_init(peer_io->is_incoming(), dh_, info_hash); auto vc_in = vc_t{}; peer_io->read_bytes(std::data(vc_in), std::size(vc_in)); if (vc_in != VC) { tr_logAddTraceHand(this, "peer's VC is not all 0"); return done(false); } peer_io->read_uint32(&crypto_provide_); tr_logAddTraceHand(this, fmt::format("crypto_provide is {}", crypto_provide_)); peer_io->read_uint16(&pad_c_len_); tr_logAddTraceHand(this, fmt::format("len(PadC) is {}", pad_c_len_)); if (pad_c_len_ > PadcMaxlen) { tr_logAddTraceHand(this, "peer's PadC is too big"); return done(false); } set_state(State::AwaitingPadC); return READ_NOW; } ReadState tr_handshake::read_pad_c(tr_peerIo* peer_io) { if (auto const needlen = pad_c_len_ + sizeof(ia_len_); peer_io->read_buffer_size() < needlen) { return READ_LATER; } // read the throwaway padc peer_io->read_buffer_discard(pad_c_len_); /* read ia_len */ peer_io->read_uint16(&ia_len_); tr_logAddTraceHand(this, fmt::format("len(IA) is {}", ia_len_)); set_state(State::AwaitingIa); return READ_NOW; } ReadState tr_handshake::read_ia(tr_peerIo* peer_io) { size_t const needlen = ia_len_; tr_logAddTraceHand(this, fmt::format("reading IA... have {}, need {}", peer_io->read_buffer_size(), needlen)); if (peer_io->read_buffer_size() < needlen) { return READ_LATER; } // B->A: ENCRYPT(VC, crypto_select, len(padD), padD), ENCRYPT2(Payload Stream) auto const& info_hash = peer_io->torrent_hash(); TR_ASSERT_MSG(info_hash != tr_sha1_digest_t{}, "read_ia requires an info_hash"); static auto constexpr BufSize = std::size(VC) + sizeof(crypto_select_) + sizeof(pad_d_len_) + HandshakeSize; auto outbuf = libtransmission::StackBuffer{}; peer_io->encrypt_init(peer_io->is_incoming(), dh_, info_hash); // send VC tr_logAddTraceHand(this, "sending vc"); outbuf.add(VC); /* send crypto_select */ crypto_select_ = get_crypto_select(encryption_mode_, crypto_provide_); if (crypto_select_ != 0U) { tr_logAddTraceHand(this, fmt::format("selecting crypto mode '{}'", crypto_select_)); outbuf.add_uint32(crypto_select_); } else { tr_logAddTraceHand(this, "peer didn't offer an encryption mode we like."); return done(false); } tr_logAddTraceHand(this, "sending pad d"); /* ENCRYPT(VC, crypto_provide, len(PadD), PadD * PadD is reserved for future extensions to the handshake... * standard practice at this time is for it to be zero-length */ outbuf.add_uint16(0U); /* maybe de-encrypt our connection */ if (crypto_select_ == CryptoProvidePlaintext) { peer_io->write(outbuf, false); TR_ASSERT(std::empty(outbuf)); // All future communications will use ENCRYPT2() peer_io->encrypt_disable(); peer_io->decrypt_disable(ia_len_); } /* now await the handshake */ set_state(State::AwaitingHandshake); return READ_NOW; } // --- ReadState tr_handshake::can_read(tr_peerIo* peer_io, void* vhandshake, size_t* piece) { auto* handshake = static_cast(vhandshake); /* no piece data in handshake */ *piece = 0; tr_logAddTraceHand(handshake, fmt::format("handling can_read; state is [{}]", handshake->state_string())); ReadState ret = READ_NOW; while (ret == READ_NOW) { switch (handshake->state()) { case State::AwaitingHandshake: ret = handshake->read_handshake(peer_io); break; case State::AwaitingPeerId: ret = handshake->read_peer_id(peer_io); break; case State::AwaitingYa: ret = handshake->read_ya(peer_io); break; case State::AwaitingPadA: ret = handshake->read_pad_a(peer_io); break; case State::AwaitingCryptoProvide: ret = handshake->read_crypto_provide(peer_io); break; case State::AwaitingPadC: ret = handshake->read_pad_c(peer_io); break; case State::AwaitingIa: ret = handshake->read_ia(peer_io); break; case State::AwaitingYb: ret = handshake->read_yb(peer_io); break; case State::AwaitingVc: ret = handshake->read_vc(peer_io); break; case State::AwaitingCryptoSelect: ret = handshake->read_crypto_select(peer_io); break; case State::AwaitingPadD: ret = handshake->read_pad_d(peer_io); break; default: TR_ASSERT_MSG(false, fmt::format("unhandled handshake state {:d}", static_cast(handshake->state()))); ret = READ_ERR; break; } } return ret; } void tr_handshake::on_error(tr_peerIo* io, tr_error const& error, void* vhandshake) { auto* handshake = static_cast(vhandshake); auto const retry = [&]() { handshake->send_handshake(io); handshake->set_state(State::AwaitingHandshake); }; auto const fail = [&]() { tr_logAddTraceHand(handshake, fmt::format("handshake socket err: {:s} ({:d})", error.message(), error.code())); handshake->done(false); }; if (io->is_utp() && !io->is_incoming() && handshake->is_state(State::AwaitingYb)) { // the peer probably doesn't speak µTP. auto const info_hash = io->torrent_hash(); auto const info = handshake->mediator_->torrent(info_hash); /* Don't mark a peer as non-µTP unless it's really a connect failure. */ if ((error.code() == ETIMEDOUT || error.code() == ECONNREFUSED) && info) { handshake->mediator_->set_utp_failed(info_hash, io->socket_address()); } if (handshake->mediator_->allows_tcp() && io->reconnect()) { retry(); return; } fail(); return; } /* 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->is_state(State::AwaitingYb) || handshake->is_state(State::AwaitingVc)) && handshake->encryption_mode_ != TR_ENCRYPTION_REQUIRED && handshake->mediator_->allows_tcp() && io->reconnect()) { tr_logAddTraceHand(handshake, "handshake failed, trying plaintext..."); retry(); return; } fail(); } // --- bool tr_handshake::build_handshake_message(tr_peerIo* io, libtransmission::BufferWriter& buf) const { auto const& info_hash = io->torrent_hash(); TR_ASSERT_MSG(info_hash != tr_sha1_digest_t{}, "build_handshake_message requires an info_hash"); auto const info = mediator_->torrent(info_hash); if (!info) { return false; } auto flags = tr_bitfield{ HandshakeFlagsBits }; flags.set(LtepFlag); flags.set(FextFlag); if (mediator_->allows_dht()) { flags.set(DhtFlag); } auto const flag_bytes = flags.raw(); buf.add(HandshakeName); buf.add(flag_bytes); buf.add(info_hash); buf.add(info->client_peer_id); return true; } bool tr_handshake::send_handshake(tr_peerIo* io) { auto msg = libtransmission::StackBuffer{}; if (!build_handshake_message(io, msg)) { return false; } TR_ASSERT(std::size(msg) == HandshakeSize); io->write(msg, false); have_sent_bittorrent_handshake_ = true; return true; } uint32_t tr_handshake::crypto_provide() const noexcept { auto provide = uint32_t{}; switch (encryption_mode_) { case TR_ENCRYPTION_REQUIRED: case TR_ENCRYPTION_PREFERRED: provide |= CryptoProvideCrypto; break; case TR_CLEAR_PREFERRED: provide |= CryptoProvideCrypto | CryptoProvidePlaintext; break; } return provide; } [[nodiscard]] uint32_t tr_handshake::get_crypto_select(tr_encryption_mode encryption_mode, uint32_t crypto_provide) noexcept { auto choices = std::array{}; int n_choices = 0; switch (encryption_mode) { case TR_ENCRYPTION_REQUIRED: choices[n_choices++] = CryptoProvideCrypto; break; case TR_ENCRYPTION_PREFERRED: choices[n_choices++] = CryptoProvideCrypto; choices[n_choices++] = CryptoProvidePlaintext; break; case TR_CLEAR_PREFERRED: choices[n_choices++] = CryptoProvidePlaintext; choices[n_choices++] = CryptoProvideCrypto; break; } for (auto const& choice : choices) { if ((crypto_provide & choice) != 0) { return choice; } } return 0; } bool tr_handshake::fire_done(bool is_connected) { maybe_recycle_dh(); if (!on_done_) { return false; } // handshake could get destroyed inside on_done, // so handle all our housekeeping *before* calling it auto cb = DoneFunc{}; std::swap(cb, on_done_); return (cb)(Result{ peer_io_, peer_id_, have_read_anything_from_peer_, is_connected }); } std::string_view tr_handshake::state_string(State state) noexcept { 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"; // 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"; } return "unknown state"; } tr_handshake::tr_handshake(Mediator* mediator, std::shared_ptr peer_io, tr_encryption_mode mode, DoneFunc on_done) : dh_{ tr_handshake::get_dh(mediator) } , on_done_{ std::move(on_done) } , peer_io_{ std::move(peer_io) } , timeout_timer_{ mediator->timer_maker().create([this]() { fire_done(false); }) } , mediator_{ mediator } , encryption_mode_{ mode } { timeout_timer_->start_single_shot(HandshakeTimeoutSec); peer_io_->set_callbacks(&tr_handshake::can_read, nullptr, &tr_handshake::on_error, this); if (is_incoming()) { set_state(State::AwaitingYa); } else if (encryption_mode_ != TR_CLEAR_PREFERRED) { send_ya(peer_io_.get()); } else { tr_logAddTraceHand(this, "sending plain handshake"); send_handshake(peer_io_.get()); set_state(State::AwaitingHandshake); } }