<reponame>FnuGk/simple-pytorrent<filename>peerwire.py<gh_stars>1-10
"""
Peer wire protocol (TCP)

The peer protocol facilitates the exchange of pieces as described in the
'metainfo file (.torrent file). Note here that the original specification also
used the term "piece" when describing the peer protocol, but as a different term
than "piece" in the metainfo file. For that reason, the term "block" will be
used in this specification to describe the data that is exchanged between peers
over the wire.
"""

from __future__ import (
    absolute_import,
    division,
    print_function,
    unicode_literals
)
import socket

import struct
import sys
import socketthread


if sys.version_info.major == 2:
    chr = unichr
    string_type = basestring
elif sys.version_info.major == 3:
    # chr should assume Unicode
    string_type = str

LENGTH_PREFIX_SIZE = 4


class HandshakeException(Exception):
    """
    Base exception for peer wire hand shake
    """

    def __init__(self, peer, error_string):
        self.peer = peer
        self.error_string = error_string

    def __str__(self):
        return "{} Refused the handshake due to: {}".format(self.peer,
                                                            self.error_string)


def generate_handshake(info_hash, peer_id):
    """
    The handshake is a required message and must be the first message
    transmitted by the client. It is (49+len(pstr)) bytes long in the form:

    <pstrlen><pstr><reserved><info_hash><peer_id>

    Where:
    pstrlen: string length of <pstr>, as a single raw byte
    pstr: string identifier of the protocol
    reserved: eight (8) reserved bytes. All current implementations use all
     zeroes. Each bit in these bytes can be used to change the behavior of the
     protocol.
    info_hash: 20-byte SHA1 hash of the info key in the meta info file. This is
     the same info_hash that is transmitted in tracker requests.
    peer_id: 20-byte string used as a unique ID for the client. This is usually
     the same peer_id that is transmitted in tracker requests

    In version 1.0 of the BitTorrent protocol:
    pstrlen = 19 and pstr = "BitTorrent protocol".

    :param info_hash:
    :param peer_id:
    :return:
    """
    pstr = b"BitTorrent protocol"
    pstrlen = bytes(chr(len(pstr)))

    reserved = b"\x00" * 8  # 8 zeroes

    handshake = pstrlen + pstr + reserved + info_hash + peer_id

    assert len(handshake) == 49 + len(pstr)
    assert pstrlen == bytes(chr(19))

    return handshake


def decode_handshake(handshake):
    """
    Decodes a received handshake.

    :param handshake: The raw byte string that is received.
    :return: Dict with the keys pstr, pstrlen, reserved, info_hash and peer_id
    """
    handshake = list(handshake)
    handshake.reverse()

    pstrlen = ord(handshake.pop())

    pstr = b""
    for _ in range(pstrlen):
        pstr += handshake.pop()

    reserved = b""
    for _ in range(8):
        reserved += handshake.pop()

    info_hash = b""
    for _ in range(20):
        info_hash += handshake.pop()

    peer_id = b""
    for _ in range(20):
        peer_id += handshake.pop()

    decoded_handshake = dict(pstr=pstr, pstrlen=pstrlen, reserved=reserved,
                             info_hash=info_hash, peer_id=peer_id)

    return decoded_handshake


class Peer(object):
    def __init__(self, ip, port, peer_id):
        self.ip = ip
        self.port = port

        self.handshake = None

        self.peers_info_hash = None
        self.has_shook_hands = False

        self.socket = socketthread.SocketThread()
        self.socket.start()

        self.peer_id = peer_id

        # Peer state
        self.am_choking = True  # this client is choking the peer
        self.am_interested = False  # this client is interested in the peer
        self.peer_choking = True  # peer is choking this client
        self.peer_interested = False  # peer is interested in this client

        self.bitfield = Bitfield()  # Contains info on what pieces the peer has

    def __str__(self):
        return "Peer: {ip}:{port}".format(ip=self.ip, port=self.port)

    def is_connected(self):
        """
        Check if the underlying socket is open
        :return: {Boolean}
        """
        return self.socket.is_connected()

    def get_reply(self, block=True, timeout=None):
        """
        Retrieves a reply from the underlying socket
        :param block: {Boolean} if the call should be blocking
        :param timeout: if block=True block for this amount of time before
        giving up
        :return: The socketthread reply
        """
        return self.socket.get_reply(block, timeout)

    def get_all_replies(self, block=True, timeout=None):
        replies = []
        reply = self.get_reply(block=block, timeout=timeout)
        while reply.status is not None:
            replies.append(reply)
            reply = self.get_reply(block=block)
        return replies

    def connect(self):
        """
        Creates and initiates a tcp connection to the peer.
        """

        address = (self.ip, self.port)
        self.socket.connect(address)

    def attempt_handshake(self, handshake):
        """
        Attempts to initiate a handshake with the peer. The has_shook_hands
        property will be updated to True if the handshake is successfully made.
        :param handshake: The handshake used to send to the peer
        :raise HandshakeException: If handshake fails
        """
        try:
            self.socket.send(handshake)
            reply = self.get_reply(block=True, timeout=1)
            if reply.status == "error":
                error = reply.payload
                raise error

            assert reply.status == "success"

            self.receive_handshake(block=True, timeout=1)

            peers_info_hash = decode_handshake(self.handshake)['info_hash']
            info_hash = decode_handshake(handshake)['info_hash']

            if not peers_info_hash == info_hash:
                self.has_shook_hands = False
                error_str = "info_hash differs, Expected {} but got {}.".format(
                    info_hash.encode('hex'),peers_info_hash.encode('hex'))
                raise HandshakeException(self, error_str)
            else:
                self.has_shook_hands = True
        except socket.error as e:
            raise HandshakeException(self, str(e))

    def send_handshake(self, handshake):
        """
        Sends the handshake to the peer
        :param handshake: The handshake to be send
        """
        self.socket.send(handshake)

    def receive_handshake(self, block=True, timeout=None):
        """
        Receives a handshake from the peer
        :param block: {Boolean} if the call should be blocking
        :param timeout: if block=True block for this amount of time before
        :raise HandshakeException: If something goes wrong raises
        HandshakeException.
        """

        try:
            pstrlen_byte_len = 1  # pstrlen is a single raw byte
            self.socket.receive_with_prefix(pstrlen_byte_len)

            reply = self.socket.get_reply(block=block, timeout=timeout)
            if reply.status is None:
                raise HandshakeException(self, "No response")
            elif reply.status == socketthread.SocketReply.ERROR:
                raise reply.payload


            pstrlen, pstr = reply.payload

            # reserved: 8, info_hash: 20, peer_id: 20
            self.socket.receive(8 + 20 + 20)

            reply = self.socket.get_reply(block=block, timeout=timeout)
            if reply.status is None:
                raise HandshakeException(self, "No response")
            elif reply.status == socketthread.SocketReply.ERROR:
                raise reply.payload

            handshake = pstrlen + pstr + reply.payload
            self.handshake = handshake
            return self.handshake
        except socket.error as e:
            raise HandshakeException(self, str(e))

    def receive_message(self):
        """
        All messages comes on the form  <length prefix><message ID><payload>.
        Where <length prefix> is a four byte big-endian value. <message ID> is a
        single decimal byte and <payload> is message depended.

        :return:
        """

        self.socket.receive_with_prefix(LENGTH_PREFIX_SIZE)

        reply = self.socket.get_reply(block=True, timeout=None)
        if reply.status != "success":
            raise reply.payload

        length_prefix, message = reply.payload
        length_prefix = struct.unpack(b">I", length_prefix)[0]
        if length_prefix == 0:
            # keep-alive: <len=0000>

            # he keep-alive message is a message with zero bytes, specified with
            # the length prefix set to zero. There is no message ID and no
            # payload. Peers may close a connection if they receive no messages
            # (keep-alive or any other message) for a certain period of time, so
            # a keep-alive message must be sent to maintain the connection alive
            # if no command have been sent for a given amount of time. This
            # amount of time is generally two minutes.
            print("keep alive")
            return

        # The message ID is a single decimal byte so just extract it from the
        # received message
        message_id = ord(message[0])

        # id 0, 1, 2 and 3 have no payload.
        if message_id >= 4:
            # The payload is the rest of the message.
            payload = message[1:]
        else:
            payload = None

        # TODO: handle received messages!
        if message_id == 0:
            # choke: <len=0001><id=0>
            # The choke message is fixed-length and has no payload.

            self.peer_choking = True
        elif message_id == 1:
            # unchoke: <len=0001><id=1>
            # The unchoke message is fixed-length and has no payload

            self.peer_choking = False
        elif message_id == 2:
            # interested: <len=0001><id=2>
            # The interested message is fixed-length and has no payload

            self.peer_interested = True
        elif message_id == 3:
            # not interested: <len=0001><id=3>
            # The not interested message is fixed-length and has no payload

            self.peer_interested = False
        elif message_id == 4:
            # have: <len=0005><id=4><piece index>
            # The have message is fixed length. The payload is the zero-based
            # index of a piece that has just been successfully downloaded and
            # verified via the hash.

            piece_index = int(payload.encode('hex'), 16)  # Convert to Integer
            self.bitfield.add_index(piece_index)
        elif message_id == 5:
            # bitfield: <len=0001+X><id=5><bitfield>
            # The bitfield message may only be sent immediately after the
            # handshaking sequence is completed, and before any other messages
            # are sent. It is optional, and need not be sent if a client has no
            # pieces. The bitfield message is variable length, where X is the
            # length of the bitfield. The payload is a bitfield representing the
            # pieces that have been successfully downloaded. The high bit in the
            # first byte corresponds to piece index 0. Bits that are cleared
            # indicated a missing piece, and set bits indicate a valid and
            # available piece. Spare bits at the end are set to zero.

            # i.e. '\xfe\xff' = 1111111011111111 (pieces 0-15, piece 7 is
            # missing). Any spare bits at the end of the last byte are left
            # unset (0)

            # Some clients (Deluge for example) send bitfield with missing
            # pieces even if it has all data. Then it sends rest of pieces as
            # have messages. They are saying this helps against ISP filtering of
            # BitTorrent protocol. It is called lazy bitfield. A bitfield of the
            # wrong length is considered an error. Clients should drop the
            # connection if they receive bitfields that are not of the correct
            # size, or if the bitfield has any of the spare bits set.

            self.bitfield = Bitfield(payload)
        elif message_id == 6:
            # request: <len=0013><id=6><index><begin><length>
            # The request message is fixed length, and is used to request a
            # block. The payload contains the following information:
            #  index: integer specifying the zero-based piece index
            #  begin: integer specifying the zero-based byte offset within
            #   the piece
            #  length: integer specifying the requested length.

            pass  # TODO: Implement this
        elif message_id == 7:
            # piece: <len=0009+X><id=7><index><begin><block>
            # piece message is variable length, where X is the length of the
            # block. The payload contains the following information:
            #  index: integer specifying the zero-based piece index
            #  begin: integer specifying the zero-based byte offset within the
            #  piece block: block of data, which is a subset of the piece
            #  specified by index

            pass  # TODO: Implement this
        elif message_id == 8:
            # cancel: <len=0013><id=8><index><begin><length
            # cancel message is fixed length, and is used to cancel block
            # requests. The payload is identical to that of the "request"
            # message. It is typically used during "End Game".

            pass  # TODO: Implement this
        elif message_id == 9:
            # port: <len=0003><id=9><listen-port>
            # The port message is sent by newer versions of the Mainline that
            # implements a DHT tracker. The listen port is the port this peer's
            # DHT node is listening on. This peer should be inserted in the
            # local routing table (if DHT tracker is supported).

            pass  # TODO: Implement this
        else:
            # !!! Unknown message id

            pass  # TODO: Implement this

        print(self, repr(message_id), repr(payload))


class Bitfield(object):
    """
    Super simple and very inefficient way to store the bit-torrent bitfield.

    We simply take a bitfield and convert it to a list of boolean values. So the
    bitfield "11011" is converted to the list [True, True, False, True, True]
    This way we can use the add_index(2) to change this to
    [True, True, True, True, True] and has_index(2) will return True. If
    add_index is called on an index that is out of range the missing indexes is
    simply set to False.
    """

    def __init__(self, bitfield=b""):
        self.bitfield = []

        # Add the bits as Boolean values in the bitfield
        for byte in bitfield:
            for bit in reversed(range(8)):  # Reverse to get correct endian
                self.bitfield.append(bool(ord(byte) >> bit & 1))

    def __str__(self):
        return "".join(["{}".format(int(bit)) for bit in self.bitfield])

    def add_index(self, index):
        while len(self.bitfield) <= index:
            self.bitfield.append(False)
        self.bitfield[index] = True

    def has_index(self, index):
        if len(self.bitfield) >= index:
            return self.bitfield[index]
        else:
            return False


if __name__ == "__main__":
    pass

"""
Peer wire protocol (TCP)

The peer protocol facilitates the exchange of pieces as described in the
'metainfo file (.torrent file). Note here that the original specification also
used the term "piece" when describing the peer protocol, but as a different term
than "piece" in the metainfo file. For that reason, the term "block" will be
used in this specification to describe the data that is exchanged between peers
over the wire.
"""

from __future__ import (
    absolute_import,
    division,
    print_function,
    unicode_literals
)
import socket

import struct
import sys
import socketthread


if sys.version_info.major == 2:
    chr = unichr
    string_type = basestring
elif sys.version_info.major == 3:
    # chr should assume Unicode
    string_type = str

LENGTH_PREFIX_SIZE = 4


class HandshakeException(Exception):
    """
    Base exception for peer wire hand shake
    """

    def __init__(self, peer, error_string):
        self.peer = peer
        self.error_string = error_string

    def __str__(self):
        return "{} Refused the handshake due to: {}".format(self.peer,
                                                            self.error_string)


def generate_handshake(info_hash, peer_id):
    """
    The handshake is a required message and must be the first message
    transmitted by the client. It is (49+len(pstr)) bytes long in the form:

    <pstrlen><pstr><reserved><info_hash><peer_id>

    Where:
    pstrlen: string length of <pstr>, as a single raw byte
    pstr: string identifier of the protocol
    reserved: eight (8) reserved bytes. All current implementations use all
     zeroes. Each bit in these bytes can be used to change the behavior of the
     protocol.
    info_hash: 20-byte SHA1 hash of the info key in the meta info file. This is
     the same info_hash that is transmitted in tracker requests.
    peer_id: 20-byte string used as a unique ID for the client. This is usually
     the same peer_id that is transmitted in tracker requests

    In version 1.0 of the BitTorrent protocol:
    pstrlen = 19 and pstr = "BitTorrent protocol".

    :param info_hash:
    :param peer_id:
    :return:
    """
    pstr = b"BitTorrent protocol"
    pstrlen = bytes(chr(len(pstr)))

    reserved = b"\x00" * 8  # 8 zeroes

    handshake = pstrlen + pstr + reserved + info_hash + peer_id

    assert len(handshake) == 49 + len(pstr)
    assert pstrlen == bytes(chr(19))

    return handshake


def decode_handshake(handshake):
    """
    Decodes a received handshake.

    :param handshake: The raw byte string that is received.
    :return: Dict with the keys pstr, pstrlen, reserved, info_hash and peer_id
    """
    handshake = list(handshake)
    handshake.reverse()

    pstrlen = ord(handshake.pop())

    pstr = b""
    for _ in range(pstrlen):
        pstr += handshake.pop()

    reserved = b""
    for _ in range(8):
        reserved += handshake.pop()

    info_hash = b""
    for _ in range(20):
        info_hash += handshake.pop()

    peer_id = b""
    for _ in range(20):
        peer_id += handshake.pop()

    decoded_handshake = dict(pstr=pstr, pstrlen=pstrlen, reserved=reserved,
                             info_hash=info_hash, peer_id=peer_id)

    return decoded_handshake


class Peer(object):
    def __init__(self, ip, port, peer_id):
        self.ip = ip
        self.port = port

        self.handshake = None

        self.peers_info_hash = None
        self.has_shook_hands = False

        self.socket = socketthread.SocketThread()
        self.socket.start()

        self.peer_id = peer_id

        # Peer state
        self.am_choking = True  # this client is choking the peer
        self.am_interested = False  # this client is interested in the peer
        self.peer_choking = True  # peer is choking this client
        self.peer_interested = False  # peer is interested in this client

        self.bitfield = Bitfield()  # Contains info on what pieces the peer has

    def __str__(self):
        return "Peer: {ip}:{port}".format(ip=self.ip, port=self.port)

    def is_connected(self):
        """
        Check if the underlying socket is open
        :return: {Boolean}
        """
        return self.socket.is_connected()

    def get_reply(self, block=True, timeout=None):
        """
        Retrieves a reply from the underlying socket
        :param block: {Boolean} if the call should be blocking
        :param timeout: if block=True block for this amount of time before
        giving up
        :return: The socketthread reply
        """
        return self.socket.get_reply(block, timeout)

    def get_all_replies(self, block=True, timeout=None):
        replies = []
        reply = self.get_reply(block=block, timeout=timeout)
        while reply.status is not None:
            replies.append(reply)
            reply = self.get_reply(block=block)
        return replies

    def connect(self):
        """
        Creates and initiates a tcp connection to the peer.
        """

        address = (self.ip, self.port)
        self.socket.connect(address)

    def attempt_handshake(self, handshake):
        """
        Attempts to initiate a handshake with the peer. The has_shook_hands
        property will be updated to True if the handshake is successfully made.
        :param handshake: The handshake used to send to the peer
        :raise HandshakeException: If handshake fails
        """
        try:
            self.socket.send(handshake)
            reply = self.get_reply(block=True, timeout=1)
            if reply.status == "error":
                error = reply.payload
                raise error

            assert reply.status == "success"

            self.receive_handshake(block=True, timeout=1)

            peers_info_hash = decode_handshake(self.handshake)['info_hash']
            info_hash = decode_handshake(handshake)['info_hash']

            if not peers_info_hash == info_hash:
                self.has_shook_hands = False
                error_str = "info_hash differs, Expected {} but got {}.".format(
                    info_hash.encode('hex'),peers_info_hash.encode('hex'))
                raise HandshakeException(self, error_str)
            else:
                self.has_shook_hands = True
        except socket.error as e:
            raise HandshakeException(self, str(e))

    def send_handshake(self, handshake):
        """
        Sends the handshake to the peer
        :param handshake: The handshake to be send
        """
        self.socket.send(handshake)

    def receive_handshake(self, block=True, timeout=None):
        """
        Receives a handshake from the peer
        :param block: {Boolean} if the call should be blocking
        :param timeout: if block=True block for this amount of time before
        :raise HandshakeException: If something goes wrong raises
        HandshakeException.
        """

        try:
            pstrlen_byte_len = 1  # pstrlen is a single raw byte
            self.socket.receive_with_prefix(pstrlen_byte_len)

            reply = self.socket.get_reply(block=block, timeout=timeout)
            if reply.status is None:
                raise HandshakeException(self, "No response")
            elif reply.status == socketthread.SocketReply.ERROR:
                raise reply.payload


            pstrlen, pstr = reply.payload

            # reserved: 8, info_hash: 20, peer_id: 20
            self.socket.receive(8 + 20 + 20)

            reply = self.socket.get_reply(block=block, timeout=timeout)
            if reply.status is None:
                raise HandshakeException(self, "No response")
            elif reply.status == socketthread.SocketReply.ERROR:
                raise reply.payload

            handshake = pstrlen + pstr + reply.payload
            self.handshake = handshake
            return self.handshake
        except socket.error as e:
            raise HandshakeException(self, str(e))

    def receive_message(self):
        """
        All messages comes on the form  <length prefix><message ID><payload>.
        Where <length prefix> is a four byte big-endian value. <message ID> is a
        single decimal byte and <payload> is message depended.

        :return:
        """

        self.socket.receive_with_prefix(LENGTH_PREFIX_SIZE)

        reply = self.socket.get_reply(block=True, timeout=None)
        if reply.status != "success":
            raise reply.payload

        length_prefix, message = reply.payload
        length_prefix = struct.unpack(b">I", length_prefix)[0]
        if length_prefix == 0:
            # keep-alive: <len=0000>

            # he keep-alive message is a message with zero bytes, specified with
            # the length prefix set to zero. There is no message ID and no
            # payload. Peers may close a connection if they receive no messages
            # (keep-alive or any other message) for a certain period of time, so
            # a keep-alive message must be sent to maintain the connection alive
            # if no command have been sent for a given amount of time. This
            # amount of time is generally two minutes.
            print("keep alive")
            return

        # The message ID is a single decimal byte so just extract it from the
        # received message
        message_id = ord(message[0])

        # id 0, 1, 2 and 3 have no payload.
        if message_id >= 4:
            # The payload is the rest of the message.
            payload = message[1:]
        else:
            payload = None

        # TODO: handle received messages!
        if message_id == 0:
            # choke: <len=0001><id=0>
            # The choke message is fixed-length and has no payload.

            self.peer_choking = True
        elif message_id == 1:
            # unchoke: <len=0001><id=1>
            # The unchoke message is fixed-length and has no payload

            self.peer_choking = False
        elif message_id == 2:
            # interested: <len=0001><id=2>
            # The interested message is fixed-length and has no payload

            self.peer_interested = True
        elif message_id == 3:
            # not interested: <len=0001><id=3>
            # The not interested message is fixed-length and has no payload

            self.peer_interested = False
        elif message_id == 4:
            # have: <len=0005><id=4><piece index>
            # The have message is fixed length. The payload is the zero-based
            # index of a piece that has just been successfully downloaded and
            # verified via the hash.

            piece_index = int(payload.encode('hex'), 16)  # Convert to Integer
            self.bitfield.add_index(piece_index)
        elif message_id == 5:
            # bitfield: <len=0001+X><id=5><bitfield>
            # The bitfield message may only be sent immediately after the
            # handshaking sequence is completed, and before any other messages
            # are sent. It is optional, and need not be sent if a client has no
            # pieces. The bitfield message is variable length, where X is the
            # length of the bitfield. The payload is a bitfield representing the
            # pieces that have been successfully downloaded. The high bit in the
            # first byte corresponds to piece index 0. Bits that are cleared
            # indicated a missing piece, and set bits indicate a valid and
            # available piece. Spare bits at the end are set to zero.

            # i.e. '\xfe\xff' = 1111111011111111 (pieces 0-15, piece 7 is
            # missing). Any spare bits at the end of the last byte are left
            # unset (0)

            # Some clients (Deluge for example) send bitfield with missing
            # pieces even if it has all data. Then it sends rest of pieces as
            # have messages. They are saying this helps against ISP filtering of
            # BitTorrent protocol. It is called lazy bitfield. A bitfield of the
            # wrong length is considered an error. Clients should drop the
            # connection if they receive bitfields that are not of the correct
            # size, or if the bitfield has any of the spare bits set.

            self.bitfield = Bitfield(payload)
        elif message_id == 6:
            # request: <len=0013><id=6><index><begin><length>
            # The request message is fixed length, and is used to request a
            # block. The payload contains the following information:
            #  index: integer specifying the zero-based piece index
            #  begin: integer specifying the zero-based byte offset within
            #   the piece
            #  length: integer specifying the requested length.

            pass  # TODO: Implement this
        elif message_id == 7:
            # piece: <len=0009+X><id=7><index><begin><block>
            # piece message is variable length, where X is the length of the
            # block. The payload contains the following information:
            #  index: integer specifying the zero-based piece index
            #  begin: integer specifying the zero-based byte offset within the
            #  piece block: block of data, which is a subset of the piece
            #  specified by index

            pass  # TODO: Implement this
        elif message_id == 8:
            # cancel: <len=0013><id=8><index><begin><length
            # cancel message is fixed length, and is used to cancel block
            # requests. The payload is identical to that of the "request"
            # message. It is typically used during "End Game".

            pass  # TODO: Implement this
        elif message_id == 9:
            # port: <len=0003><id=9><listen-port>
            # The port message is sent by newer versions of the Mainline that
            # implements a DHT tracker. The listen port is the port this peer's
            # DHT node is listening on. This peer should be inserted in the
            # local routing table (if DHT tracker is supported).

            pass  # TODO: Implement this
        else:
            # !!! Unknown message id

            pass  # TODO: Implement this

        print(self, repr(message_id), repr(payload))


class Bitfield(object):
    """
    Super simple and very inefficient way to store the bit-torrent bitfield.

    We simply take a bitfield and convert it to a list of boolean values. So the
    bitfield "11011" is converted to the list [True, True, False, True, True]
    This way we can use the add_index(2) to change this to
    [True, True, True, True, True] and has_index(2) will return True. If
    add_index is called on an index that is out of range the missing indexes is
    simply set to False.
    """

    def __init__(self, bitfield=b""):
        self.bitfield = []

        # Add the bits as Boolean values in the bitfield
        for byte in bitfield:
            for bit in reversed(range(8)):  # Reverse to get correct endian
                self.bitfield.append(bool(ord(byte) >> bit & 1))

    def __str__(self):
        return "".join(["{}".format(int(bit)) for bit in self.bitfield])

    def add_index(self, index):
        while len(self.bitfield) <= index:
            self.bitfield.append(False)
        self.bitfield[index] = True

    def has_index(self, index):
        if len(self.bitfield) >= index:
            return self.bitfield[index]
        else:
            return False


if __name__ == "__main__":
    pass
Peer wire protocol (TCP)  The peer protocol facilitates the exchange of pieces as described in the 'metainfo file (.torrent file). Note here that the original specification also used the term "piece" when describing the peer protocol, but as a different term than "piece" in the metainfo file. For that reason, the term "block" will be used in this specification to describe the data that is exchanged between peers over the wire. # chr should assume Unicode Base exception for peer wire hand shake The handshake is a required message and must be the first message     transmitted by the client. It is (49+len(pstr)) bytes long in the form:      <pstrlen><pstr><reserved><info_hash><peer_id>      Where:     pstrlen: string length of <pstr>, as a single raw byte     pstr: string identifier of the protocol     reserved: eight (8) reserved bytes. All current implementations use all      zeroes. Each bit in these bytes can be used to change the behavior of the      protocol.     info_hash: 20-byte SHA1 hash of the info key in the meta info file. This is      the same info_hash that is transmitted in tracker requests.     peer_id: 20-byte string used as a unique ID for the client. This is usually      the same peer_id that is transmitted in tracker requests      In version 1.0 of the BitTorrent protocol:     pstrlen = 19 and pstr = "BitTorrent protocol".      :param info_hash:     :param peer_id:     :return: # 8 zeroes Decodes a received handshake.      :param handshake: The raw byte string that is received.     :return: Dict with the keys pstr, pstrlen, reserved, info_hash and peer_id # Peer state # this client is choking the peer # this client is interested in the peer # peer is choking this client # peer is interested in this client # Contains info on what pieces the peer has Check if the underlying socket is open         :return: {Boolean} Retrieves a reply from the underlying socket         :param block: {Boolean} if the call should be blocking         :param timeout: if block=True block for this amount of time before         giving up         :return: The socketthread reply Creates and initiates a tcp connection to the peer. Attempts to initiate a handshake with the peer. The has_shook_hands         property will be updated to True if the handshake is successfully made.         :param handshake: The handshake used to send to the peer         :raise HandshakeException: If handshake fails Sends the handshake to the peer         :param handshake: The handshake to be send Receives a handshake from the peer         :param block: {Boolean} if the call should be blocking         :param timeout: if block=True block for this amount of time before         :raise HandshakeException: If something goes wrong raises         HandshakeException. # pstrlen is a single raw byte # reserved: 8, info_hash: 20, peer_id: 20 All messages comes on the form  <length prefix><message ID><payload>.         Where <length prefix> is a four byte big-endian value. <message ID> is a         single decimal byte and <payload> is message depended.          :return: # keep-alive: <len=0000> # he keep-alive message is a message with zero bytes, specified with # the length prefix set to zero. There is no message ID and no # payload. Peers may close a connection if they receive no messages # (keep-alive or any other message) for a certain period of time, so # a keep-alive message must be sent to maintain the connection alive # if no command have been sent for a given amount of time. This # amount of time is generally two minutes. # The message ID is a single decimal byte so just extract it from the # received message # id 0, 1, 2 and 3 have no payload. # The payload is the rest of the message. # TODO: handle received messages! # choke: <len=0001><id=0> # The choke message is fixed-length and has no payload. # unchoke: <len=0001><id=1> # The unchoke message is fixed-length and has no payload # interested: <len=0001><id=2> # The interested message is fixed-length and has no payload # not interested: <len=0001><id=3> # The not interested message is fixed-length and has no payload # have: <len=0005><id=4><piece index> # The have message is fixed length. The payload is the zero-based # index of a piece that has just been successfully downloaded and # verified via the hash. # Convert to Integer # bitfield: <len=0001+X><id=5><bitfield> # The bitfield message may only be sent immediately after the # handshaking sequence is completed, and before any other messages # are sent. It is optional, and need not be sent if a client has no # pieces. The bitfield message is variable length, where X is the # length of the bitfield. The payload is a bitfield representing the # pieces that have been successfully downloaded. The high bit in the # first byte corresponds to piece index 0. Bits that are cleared # indicated a missing piece, and set bits indicate a valid and # available piece. Spare bits at the end are set to zero. # i.e. '\xfe\xff' = 1111111011111111 (pieces 0-15, piece 7 is # missing). Any spare bits at the end of the last byte are left # unset (0) # Some clients (Deluge for example) send bitfield with missing # pieces even if it has all data. Then it sends rest of pieces as # have messages. They are saying this helps against ISP filtering of # BitTorrent protocol. It is called lazy bitfield. A bitfield of the # wrong length is considered an error. Clients should drop the # connection if they receive bitfields that are not of the correct # size, or if the bitfield has any of the spare bits set. # request: <len=0013><id=6><index><begin><length> # The request message is fixed length, and is used to request a # block. The payload contains the following information: #  index: integer specifying the zero-based piece index #  begin: integer specifying the zero-based byte offset within #   the piece #  length: integer specifying the requested length. # TODO: Implement this # piece: <len=0009+X><id=7><index><begin><block> # piece message is variable length, where X is the length of the # block. The payload contains the following information: #  index: integer specifying the zero-based piece index #  begin: integer specifying the zero-based byte offset within the #  piece block: block of data, which is a subset of the piece #  specified by index # TODO: Implement this # cancel: <len=0013><id=8><index><begin><length # cancel message is fixed length, and is used to cancel block # requests. The payload is identical to that of the "request" # message. It is typically used during "End Game". # TODO: Implement this # port: <len=0003><id=9><listen-port> # The port message is sent by newer versions of the Mainline that # implements a DHT tracker. The listen port is the port this peer's # DHT node is listening on. This peer should be inserted in the # local routing table (if DHT tracker is supported). # TODO: Implement this # !!! Unknown message id # TODO: Implement this Super simple and very inefficient way to store the bit-torrent bitfield.      We simply take a bitfield and convert it to a list of boolean values. So the     bitfield "11011" is converted to the list [True, True, False, True, True]     This way we can use the add_index(2) to change this to     [True, True, True, True, True] and has_index(2) will return True. If     add_index is called on an index that is out of range the missing indexes is     simply set to False. # Add the bits as Boolean values in the bitfield # Reverse to get correct endian