PeerConnection

class PeerConnection(conn: ByteStream, infoHash: Sha1Hash, ourPeerId: Sha1Hash, numPieces: Int, hasV2: Boolean = false)

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

Link copied to clipboard
constructor(conn: ByteStream, infoHash: Sha1Hash, ourPeerId: Sha1Hash, numPieces: Int, hasV2: Boolean = false)

Types

Link copied to clipboard
object Companion

Properties

Link copied to clipboard

We are choking the remote peer. See PeerState.amChoking.

Link copied to clipboard

We are interested in the remote peer. See PeerState.amInterested.

Link copied to clipboard

Set once disconnect is called or the receive loop terminates; blocks further sends.

Link copied to clipboard

True iff the remote peer's id equals ourPeerId, which means a self-connect. libtorrent rejects these (pid == m_our_peer_iderrors::self_connection). Returns false until a handshake has populated remotePeerId.

Link copied to clipboard

A label for the remote end, for logging.

Link copied to clipboard

The remote peer's id, set once performHandshake succeeds; null before.

Link copied to clipboard

The remote peer's 8 reserved/capability bytes, set by performHandshake.

Link copied to clipboard

The mutable choke/interest/availability state for this connection.

Link copied to clipboard

The remote peer set the DHT bit (BEP-5). Set during the handshake.

Link copied to clipboard

The remote peer set the extension-protocol bit (BEP-10). Set during the handshake.

Link copied to clipboard

The remote peer set the Fast-extension bit (BEP-6). Set during the handshake.

Link copied to clipboard

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.

Link copied to clipboard

The pieces the remote peer has advertised. See PeerState.theirBitfield.

Link copied to clipboard

The remote peer is choking us. See PeerState.theirChoking.

Link copied to clipboard

The remote peer is interested in us. See PeerState.theirInterested.

Functions

Link copied to clipboard

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_hashwrite_handshake). conn must be positioned just past the remote's handshake bytes.

Link copied to clipboard

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.

Link copied to clipboard

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.

Link copied to clipboard
suspend fun receiveLoop(maxMessageLength: Int = DEFAULT_MAX_MESSAGE_LENGTH, onMessage: suspend (PeerMessage) -> Unit)

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.

Link copied to clipboard
suspend fun receiveLoopCatching(maxMessageLength: Int = DEFAULT_MAX_MESSAGE_LENGTH, onMessage: suspend (PeerMessage) -> Unit)

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.

Link copied to clipboard
suspend fun sendBitfield(bitfield: Bitfield)

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.

Link copied to clipboard
suspend fun sendCancel(piece: Int, begin: Int, length: Int)

Send cancel for a previously requested block (write_cancel).

Link copied to clipboard
suspend fun sendChoke()

Send choke and set PeerState.amChoking (write_choke).

Link copied to clipboard
suspend fun sendExtended(extId: Int, payload: ByteArray)

Send an extended message (BEP-10). extId 0 is the extension handshake; any other id is one the peer advertised in its handshake m dict. payload is the bencoded (+ optional appended data) extension message body.

Link copied to clipboard
suspend fun sendHashes(msg: PeerMessage.Hashes)

Send hashes (id 22, BEP-52): the merkle hashes answering a request (write_hashes).

Link copied to clipboard

Send hash_reject (id 23, BEP-52) to decline a hash_request (write_hash_reject).

Link copied to clipboard

Send hash_request (id 21, BEP-52) to ask for merkle hashes (write_hash_request).

Link copied to clipboard
suspend fun sendHave(piece: Int)

Send have for piece (write_have), announcing we just completed it.

Link copied to clipboard
suspend fun sendHaveAll()

Send have_all (BEP-6, write_have_all). It replaces a full bitfield when seeding.

Link copied to clipboard
suspend fun sendHaveNone()

Send have_none (BEP-6, write_have_none). It replaces an empty bitfield.

Link copied to clipboard
suspend fun sendInterested()

Send interested and set PeerState.amInterested (write_interested).

Link copied to clipboard
suspend fun sendKeepAlive()

Send a keep-alive (four zero bytes, write_keepalive).

Link copied to clipboard
suspend fun sendNotInterested()

Send not_interested and clear PeerState.amInterested (write_not_interested).

Link copied to clipboard
suspend fun sendPiece(piece: Int, begin: Int, block: ByteArray)

Send a piece block: block bytes at begin within piece (write_piece). The orchestrator reads the block from disk and calls this when honouring a request from an unchoked, interested peer.

Link copied to clipboard
suspend fun sendPort(port: Int)

Send port (BEP-5, write_dht_port) advertising our DHT UDP port.

Link copied to clipboard
suspend fun sendRejectRequest(piece: Int, begin: Int, length: Int)

Send reject_request (BEP-6, write_reject_request) to refuse a request we will not serve.

Link copied to clipboard
suspend fun sendRequest(piece: Int, begin: Int, length: Int)

Send request for length bytes at begin within piece (write_request).

Link copied to clipboard
suspend fun sendUnchoke()

Send unchoke and clear PeerState.amChoking (write_unchoke).