class PeerProxy(object): class _States(object): (Awaiting_Handshake, Awaiting_Connection, Handshake_Initiated, Bitfield_Allowed, Peer_to_Peer, Disconnected) = range(6) def __init__(self, client, peer_id, addr, reactor, protocol=None, info_hash=None): self._client = client self._reactor = reactor self._protocol = protocol self._info_hash = info_hash self._peer_id = peer_id self._addr = addr self._choked = True self._interested = False self._peer_choked = True self._peer_interested = False if len(peer_id) != 20: raise ValueError("Peer id must be 20 bytes long") if protocol is None: if info_hash is None or len(info_hash) != 20: raise ValueError("Info hash must be a 20 byte value") self._translator = None host, port = addr d = (TCP4ClientEndpoint(reactor, host, port) .connect(ProtocolAdapterFactory(self))) d.addErrback(self.connection_failed) self._state = self._States.Awaiting_Connection else: self._setup_handshake_translator() self._state = self._States.Awaiting_Handshake def _setup_handshake_translator(self): self._translator = HandshakeTranslator(self, self._protocol) def _drop_connection(self, notify_client=True): if self._translator is not None: self._translator.unset_receiver() self._translator.unset_readerwriter() self._translator = None if self._protocol is not None: self._protocol.stop() self._state = self._States.Disconnected if notify_client: self._client.peer_unconnected(self) def _valid_rx_state(self): if self._state != self._States.Peer_to_Peer: if self._state == self._States.Bitfield_Allowed: self._state = self._States.Peer_to_Peer else: if self._state != self._States.Disconnected: self._drop_connection() return False return True def _valid_tx_state(self): if self._state != self._States.Peer_to_Peer: if self._state == self._States.Bitfield_Allowed: self._state = self._States.Peer_to_Peer else: return False return True def addr(self): return self._addr def is_interested(self): return self._interested def is_choked(self): return self._choked def is_peer_choked(self): return self._peer_choked def is_peer_interested(self): return self._peer_interested # Callbacks which result from TCP4ClientEndpoint.connect() def connection_complete(self, protocol): self._protocol = protocol self._setup_handshake_translator() self._translator.tx_handshake(0, self._info_hash, self._peer_id) self._state = self._States.Handshake_Initiated def connection_failed(self, reason): self._client.peer_unconnected(self) # Translator callbacks def connection_lost(self): self._drop_connection() # HandshakeTranslator callbacks def rx_handshake(self, reserved, info_hash, peer_id): if self._state == self._States.Handshake_Initiated: if info_hash != self._info_hash: self._drop_connection() else: self._translator.unset_receiver() self._translator.unset_readerwriter() self._translator = PeerWireTranslator(self, self._protocol) self._state = self._States.Bitfield_Allowed self._translator.tx_bitfield(self._client.get_bitfield()) def rx_non_handshake(self): self._drop_connection() # PeerWireTranslator callbacks def rx_bitfield(self, bitfield): if self._state == self._States.Bitfield_Allowed: self._state = self._States.Peer_to_Peer self._client.peer_bitfield(self, bitfield) else: self._drop_connection() def rx_keep_alive(self): pass def rx_choke(self): if self._valid_rx_state(): self._peer_choked = True self._client.peer_choked(self) def rx_unchoke(self): if self._valid_rx_state(): self._peer_choked = False self._client.peer_unchoked(self) def rx_interested(self): if self._valid_rx_state(): self._peer_interested = True self._client.peer_interested(self) def rx_not_interested(self): if self._valid_rx_state(): self._peer_interested = False self._client.peer_not_interested(self) def rx_have(self, index): if self._valid_rx_state(): self._client.peer_has(self, index) def rx_request(self, index, begin, length): if self._valid_rx_state(): self._client.peer_requests(self, index, begin, length) def rx_piece(self, index, begin, buf): if self._valid_rx_state(): self._client.peer_sent_block(self, index, begin, buf) def rx_cancel(self, index, begin, length): if self._valid_rx_state(): self._client.peer_canceled(self, index, begin, length) # Client calls def drop_connection(self): self._drop_connection(False) def choke(self): if self._valid_tx_state(): self._choked = True self._translator.tx_choke() def unchoke(self): if self._valid_tx_state(): self._choked = False self._translator.tx_unchoke() def interested(self): if self._valid_tx_state(): self._interested = True self._translator.tx_interested() def not_interested(self): if self._valid_tx_state(): self._interested = False self._translator.tx_not_interested() def have(self, index): if self._valid_tx_state(): self._translator.tx_have(index) def request(self, index, begin, length): if self._valid_tx_state(): self._translator.tx_request(index, begin, length) def piece(self, index, begin, buf, offset): if self._valid_tx_state(): self._translator.tx_piece(index, begin, buf) def cancel(self, index, begin, length): if self._valid_tx_state(): self._translator.tx_cancel(index, begin, length)