PeerConnection
One BitTorrent peer session over a single byte-duplex. This is the coroutine port of libtorrent's peer_connection and bt_peer_connection (src/peer_connection.cpp, src/bt_peer_connection.cpp). It drives the wire protocol for one peer.
libtorrent drives this connection through Boost.Asio completion callbacks (on_receive, on_send) feeding a hand-rolled receive-buffer state machine (m_recv_buffer, state_t). Here the same protocol logic runs as straight-line suspending code: performHandshake suspends on the 68-byte exchange, then receiveLoop frames and decodes messages one at a time. There are no callbacks and no explicit state_t enum. The suspension points are the states.
Scope. This class owns the parts of peer_connection that are per-connection and local: the handshake, message framing/decoding, the four choke/interest flags (PeerState), and the peer's advertised piece set. It deliberately does not own the swarm-wide policy that libtorrent also crams into peer_connection:
the request scheduler (
request_a_block,send_block_requests, the download-queue / pipeline depth),the unchoke algorithm (
maybe_unchoke_this_peer, choke rotation),piece verification and disk writes.
Those need the io.github.yuroyami.kitetorrent.picker.PiecePicker and io.github.yuroyami.kitetorrent.session.disk.DiskIo, which are shared across all peers, so they live in the session orchestrator above this class. receiveLoop surfaces every decoded PeerMessage to an onMessage callback after updating the local state, which is exactly the seam the orchestrator hooks: e.g. on a PeerMessage.Unchoke it asks the picker for blocks and calls sendRequest, on a PeerMessage.Piece it hands the block to disk. See the per-message notes below for what is handled here vs. deferred.
Threading. A PeerConnection is single-reader/single-writer like its C++ original (is_single_thread()): run receiveLoop in one coroutine and call the send* helpers from the orchestrator's coroutine. Concurrent writers would interleave frames on the wire.
Constructors
Properties
We are choking the remote peer. See PeerState.amChoking.
We are interested in the remote peer. See PeerState.amInterested.
Set once disconnect is called or the receive loop terminates; blocks further sends.
True iff the remote peer's id equals ourPeerId, which means a self-connect. libtorrent rejects these (pid == m_our_peer_id ⇒ errors::self_connection). Returns false until a handshake has populated remotePeerId.
A label for the remote end, for logging.
The remote peer's id, set once performHandshake succeeds; null before.
The remote peer's 8 reserved/capability bytes, set by performHandshake.
The remote peer set the DHT bit (BEP-5). Set during the handshake.
The remote peer set the extension-protocol bit (BEP-10). Set during the handshake.
The remote peer set the Fast-extension bit (BEP-6). Set during the handshake.
The remote peer set the BEP-52 v2-upgrade bit. Read from the peer's reserved bytes during the handshake; false until a handshake completes.
The pieces the remote peer has advertised. See PeerState.theirBitfield.
The remote peer is choking us. See PeerState.theirChoking.
The remote peer is interested in us. See PeerState.theirInterested.
Functions
Complete an inbound handshake. The accept loop has already read the remote's 68-byte handshake (to route by info-hash); we validate it and write ours. This is the incoming arm of bt_peer_connection (read_info_hash → write_handshake). conn must be positioned just past the remote's handshake bytes.
Mark this connection disconnected so any subsequent send* call fails immediately with ProtocolException instead of writing onto a stream the orchestrator is about to close. Idempotent and safe to call from the orchestrator on a self-connect, a duplicate peer-id, or a protocol error. This is the analogue of libtorrent's disconnect(...) decision point.
Perform the BitTorrent handshake: write our 68-byte handshake, then read and validate the peer's. Ports write_handshake() (the send) and the read_info_hash / read_peer_id arms of on_receive (the receive + validation), collapsed into one suspending exchange.
Read and dispatch peer-wire messages until the stream closes (or the coroutine is cancelled). Ports the post-handshake framing of on_receive: read the 4-byte big-endian length prefix, then the body, then decode and dispatch. This is the suspending equivalent of libtorrent waiting for packet_size bytes in m_recv_buffer before calling dispatch_message.
Like receiveLoop but swallows the end-of-stream / cancellation exception so a normal peer disconnect returns instead of throwing. Protocol violations (ProtocolException, BadFrameLengthException) still propagate. A peer that breaks framing must be disconnected, which libtorrent does the same way.
Send our bitfield (write_bitfield). libtorrent sends this once, right after the handshake, before any other message. bitfield is sent verbatim; its size should match the torrent's piece count.
Send cancel for a previously requested block (write_cancel).
Send choke and set PeerState.amChoking (write_choke).
Send hashes (id 22, BEP-52): the merkle hashes answering a request (write_hashes).
Send hash_reject (id 23, BEP-52) to decline a hash_request (write_hash_reject).
Send hash_request (id 21, BEP-52) to ask for merkle hashes (write_hash_request).
Send have_all (BEP-6, write_have_all). It replaces a full bitfield when seeding.
Send have_none (BEP-6, write_have_none). It replaces an empty bitfield.
Send interested and set PeerState.amInterested (write_interested).
Send a keep-alive (four zero bytes, write_keepalive).
Send not_interested and clear PeerState.amInterested (write_not_interested).
Send reject_request (BEP-6, write_reject_request) to refuse a request we will not serve.
Send unchoke and clear PeerState.amChoking (write_unchoke).