Example #1
0
    def __init__(self, torrent_file):
        with open(torrent_file, 'r') as t:
            parsed_torrent = t.read()
        self.info_dict = bencode.bdecode(parsed_torrent)
        self.tracker_url = self.info_dict['announce']
        self.info_hash = hashlib.sha1(bencode.bencode(
            self.info_dict['info'])).digest()
        self.info_hash_readable = hashlib.sha1(
            bencode.bencode(self.info_dict['info'])).hexdigest()

        if self.info_dict['info'].get('name'):
            self.name = self.info_dict['info']['name']
        self.encoding = self.info_dict.get('encoding', None)

        if self.info_dict['info'].get('files'):
            self.no_of_files = len(self.info_dict['info']['files'])
            self.total_length = 0
            for files in self.info_dict['info']['files']:
                self.total_length += files['length']

        else:
            self.no_of_files = 1
            self.total_length = self.info_dict['info']['length']

        self.subpieces = self.info_dict['info']['pieces']
        self.piece_length = self.info_dict['info']['piece length']
        self.list_of_subpieces_hashes = []
        for i in range(0, len(self.subpieces), 20):
            self.list_of_subpieces_hashes.append(self.subpieces[i:i + 20])
        self.no_of_subpieces = len(self.list_of_subpieces_hashes)
        self.bitfield = Bitfield(self)
        self.block_size = 16384
Example #2
0
def test_stops_at_backlog():
    ds = DummyStorage([[(0, 2), (2, 2), (4, 2), (6, 2)]])
    events = []
    d = Downloader(ds, DummyPicker(len(ds.remaining), events), 2, 15, 1,
                   Measure(15), 10)
    sd = d.make_download(DummyConnection(events))
    assert events == []
    assert ds.remaining == [[(0, 2), (2, 2), (4, 2), (6, 2)]]
    assert ds.active == [[]]
    sd.got_have_bitfield(Bitfield(1, chr(0x80)))
    assert events == ['got have', 'interested']
    del events[:]
    assert ds.remaining == [[(0, 2), (2, 2), (4, 2), (6, 2)]]
    assert ds.active == [[]]
    sd.got_unchoke()
    assert events == [
        'requested', ('request', 0, 6, 2), 'requested', ('request', 0, 4, 2)
    ]
    del events[:]
    assert ds.remaining == [[(0, 2), (2, 2)]]
    assert ds.active == [[(4, 2), (6, 2)]]
    sd.got_piece(0, 4, 'ab')
    assert events == ['requested', ('request', 0, 2, 2)]
    del events[:]
    assert ds.remaining == [[(0, 2)]]
    assert ds.active == [[(2, 2), (6, 2)]]
Example #3
0
 def __receive_bitfield__(self, peer):
     data = peer.receive()
     msg = Message.deserialize(data)
     if msg.id != MessageCodes.BITFIELD:
         raise ConnectionError("expected BITFIELD, got {}".format(
             MessageCodes(msg.id).name))
     return Bitfield(msg.message)
Example #4
0
    def getBitfield(self):
        bfNeed = Bitfield(self.pieceNum)
        for f in self.files:
            for i in range(f.idx0_piece, f.idx1_piece):
                bfNeed[i] = 1

        bfHave = Bitfield(self.pieceNum)
        for i in range(self.pieceNum):
            try:
                ds = self[i]
                if len(ds) == 1:
                    beg, dat = ds[0]
                    if self.doHashTest(i, dat):
                        bfHave[i] = 1
                        bfNeed[i] = 0
            except BTFileError as error:
                pass

        return bfHave, bfNeed
Example #5
0
    def _bitfield(self, data):
        pm = self.pieceManager
        bf = Bitfield(pm.pieces_size, data)
        self.peer_bitfield = bf

        if self.pieceManager.amInterested(bf):
            self.interested(True)
            self.__pieceRequest()
        else:
            self.interested(False)
Example #6
0
 def __init__(self, downloader, connection):
     self.downloader = downloader
     self.connection = connection
     self.choked = True
     self.interested = False
     self.active_requests = []
     self.measure = Measure(downloader.max_rate_period)
     self.have = Bitfield(downloader.numpieces)
     self.last = 0
     self.example_interest = None
Example #7
0
def chunk_file(file_obj, bitfield_size):
    """
        Chunk a binary file into Bitfields of the given size.
    """
    # TODO: read the file in chunks
    data = file_obj.read()
    data = Bitfield(int.from_bytes(data, 'little'))
    while data:
        chunk = data[:bitfield_size]
        chunk.width = bitfield_size
        yield chunk
        data >>= bitfield_size
Example #8
0
    def start(self):
        if not self.protocol:
            return

        self.status = 'running'
        self.btm = self.protocol.factory.btm
        self.pieceManager = self.btm.pieceManager
        pm = self.pieceManager
        self.peer_bitfield = Bitfield(pm.pieces_size)
        self.downloadSpeedMonitor.start()
        self.downloadSpeedMonitor.registerObserver(
            self.protocol.factory.downloadSpeedMonitor)
Example #9
0
    def test_byte_divisible_2(self):
        nibbles = []
        for byte in self.bytes:
            bits = Bitfield(byte, width=8)
            left, right = bits[:4], bits[4:]
            left.width, right.width = 4, 4
            nibbles.append(left)
            nibbles.append(right)

        with open(self.filename, 'rb') as f:
            chunker = chunk_file(f, 4)
            for read, actual in zip(chunker, nibbles):
                self.assertEqual(read, actual)
Example #10
0
 def __init__(self, downloader, connection):
     self.downloader = downloader
     self.connection = connection
     # Whether the peer is choking this client.
     self.choked = True
     # Whether this client is interested in data the peer has.
     self.interested = False
     # The (index, begin, length) tuples this client has requested from the peer.
     self.active_requests = []
     # Measures the download rate from the peer.
     self.measure = Measure(downloader.max_rate_period)
     # The pieces the peer has.
     self.have = Bitfield(downloader.numpieces)
     # The last time this client has gotten data from the peer.
     self.last = 0
     self.example_interest = None
Example #11
0
    def finishHandshake(self):
        self.btm = self.factory.btm

        self.bitfield = Bitfield(self.btm.metainfo.pieces_size)

        self.upload = BTUpload(self)
        self.download = BTDownload(self)
        self.upload.start()
        self.__uploadMonitor = self.upload._uploadMonitor
        self.download.start()
        self.__downloadMonitor = self.download._downloadMonitor

        self.send_bitfield(self.btm.pieceManager.bitfield)

        self.send_keep_alive()

        if self.btm.connectionManager.isAlreadyConnected(self.peer_id):
            # Already connected, dropping the connection
            reactor.callLater(0, self.transport.loseConnection)
        else:
            self.factory.addActiveConnection(self.peer_id, self)

        self.status = 'started'
Example #12
0
 def got_message(self, connection, message):
     c = self.connections[connection]
     t = message[0]
     if t == BITFIELD and c.got_anything:
         connection.close()
         return
     c.got_anything = True
     if (t in [CHOKE, UNCHOKE, INTERESTED, NOT_INTERESTED]
             and len(message) != 1):
         connection.close()
         return
     if t == CHOKE:
         c.download.got_choke()
     elif t == UNCHOKE:
         c.download.got_unchoke()
     elif t == INTERESTED:
         c.upload.got_interested()
     elif t == NOT_INTERESTED:
         c.upload.got_not_interested()
     elif t == HAVE:
         if len(message) != 5:
             connection.close()
             return
         i = toint(message[1:])
         if i >= self.numpieces:
             connection.close()
             return
         c.download.got_have(i)
     elif t == BITFIELD:
         try:
             b = Bitfield(self.numpieces, message[1:])
         except ValueError:
             connection.close()
             return
         c.download.got_have_bitfield(b)
     elif t == REQUEST:
         if len(message) != 13:
             connection.close()
             return
         i = toint(message[1:5])
         if i >= self.numpieces:
             connection.close()
             return
         c.upload.got_request(i, toint(message[5:9]), toint(message[9:]))
     elif t == CANCEL:
         if len(message) != 13:
             connection.close()
             return
         i = toint(message[1:5])
         if i >= self.numpieces:
             connection.close()
             return
         c.upload.got_cancel(i, toint(message[5:9]), toint(message[9:]))
     elif t == PIECE:
         if len(message) <= 9:
             connection.close()
             return
         i = toint(message[1:5])
         if i >= self.numpieces:
             connection.close()
             return
         if c.download.got_piece(i, toint(message[5:9]), message[9:]):
             for co in self.connections.values():
                 co.send_have(i)
     else:
         connection.close()
Example #13
0
    def __init__(self,
                 storage,
                 request_size,
                 hashes,
                 piece_size,
                 finished,
                 failed,
                 statusfunc=dummy_status,
                 flag=Event(),
                 check_hashes=True,
                 data_flunked=dummy_data_flunked):
        self.storage = storage
        self.request_size = request_size
        self.hashes = hashes
        self.piece_size = piece_size
        self.data_flunked = data_flunked
        self.total_length = storage.get_total_length()
        self.amount_left = self.total_length
        if self.total_length <= piece_size * (len(hashes) - 1):
            raise ValueError, 'bad data from tracker - total too small'
        if self.total_length > piece_size * len(hashes):
            raise ValueError, 'bad data from tracker - total too big'
        self.finished = finished
        self.failed = failed
        self.numactive = [0] * len(hashes)
        self.inactive_requests = [1] * len(hashes)
        self.amount_inactive = self.total_length
        self.endgame = False
        self.have = Bitfield(len(hashes))
        self.waschecked = [check_hashes] * len(hashes)
        self.places = {}
        self.holes = []
        if len(hashes) == 0:
            finished()
            return
        targets = {}
        total = len(hashes)
        for i in xrange(len(hashes)):
            if not self._waspre(i):
                targets.setdefault(hashes[i], []).append(i)
                total -= 1
        numchecked = 0.0
        if total and check_hashes:
            statusfunc({
                "activity": 'checking existing file',
                "fractionDone": 0
            })

        def markgot(piece, pos, self=self, check_hashes=check_hashes):
            self.places[piece] = pos
            self.have[piece] = True
            self.amount_left -= self._piecelen(piece)
            self.amount_inactive -= self._piecelen(piece)
            self.inactive_requests[piece] = None
            self.waschecked[piece] = check_hashes

        lastlen = self._piecelen(len(hashes) - 1)
        for i in xrange(len(hashes)):
            if not self._waspre(i):
                self.holes.append(i)
            elif not check_hashes:
                markgot(i, i)
            else:
                sh = sha1(self.storage.read(piece_size * i, lastlen))
                sp = sh.digest()
                sh.update(
                    self.storage.read(piece_size * i + lastlen,
                                      self._piecelen(i) - lastlen))
                s = sh.digest()
                if s == hashes[i]:
                    markgot(i, i)
                elif targets.get(s) and self._piecelen(i) == self._piecelen(
                        targets[s][-1]):
                    markgot(targets[s].pop(), i)
                elif not self.have[len(hashes) - 1] and sp == hashes[-1] and (
                        i == len(hashes) - 1
                        or not self._waspre(len(hashes) - 1)):
                    markgot(len(hashes) - 1, i)
                else:
                    self.places[i] = i
                if flag.isSet():
                    return
                numchecked += 1
                statusfunc({
                    'fractionDone':
                    1 - float(self.amount_left) / self.total_length
                })
        if self.amount_left == 0:
            finished()
Example #14
0
class StorageWrapper:
    def __init__(self,
                 storage,
                 request_size,
                 hashes,
                 piece_size,
                 finished,
                 failed,
                 statusfunc=dummy_status,
                 flag=Event(),
                 check_hashes=True,
                 data_flunked=dummy_data_flunked):
        self.storage = storage
        self.request_size = request_size
        self.hashes = hashes
        self.piece_size = piece_size
        self.data_flunked = data_flunked
        self.total_length = storage.get_total_length()
        self.amount_left = self.total_length
        if self.total_length <= piece_size * (len(hashes) - 1):
            raise ValueError, 'bad data from tracker - total too small'
        if self.total_length > piece_size * len(hashes):
            raise ValueError, 'bad data from tracker - total too big'
        self.finished = finished
        self.failed = failed
        self.numactive = [0] * len(hashes)
        self.inactive_requests = [1] * len(hashes)
        self.amount_inactive = self.total_length
        self.endgame = False
        self.have = Bitfield(len(hashes))
        self.waschecked = [check_hashes] * len(hashes)
        self.places = {}
        self.holes = []
        if len(hashes) == 0:
            finished()
            return
        targets = {}
        total = len(hashes)
        for i in xrange(len(hashes)):
            if not self._waspre(i):
                targets.setdefault(hashes[i], []).append(i)
                total -= 1
        numchecked = 0.0
        if total and check_hashes:
            statusfunc({
                "activity": 'checking existing file',
                "fractionDone": 0
            })

        def markgot(piece, pos, self=self, check_hashes=check_hashes):
            self.places[piece] = pos
            self.have[piece] = True
            self.amount_left -= self._piecelen(piece)
            self.amount_inactive -= self._piecelen(piece)
            self.inactive_requests[piece] = None
            self.waschecked[piece] = check_hashes

        lastlen = self._piecelen(len(hashes) - 1)
        for i in xrange(len(hashes)):
            if not self._waspre(i):
                self.holes.append(i)
            elif not check_hashes:
                markgot(i, i)
            else:
                sh = sha1(self.storage.read(piece_size * i, lastlen))
                sp = sh.digest()
                sh.update(
                    self.storage.read(piece_size * i + lastlen,
                                      self._piecelen(i) - lastlen))
                s = sh.digest()
                if s == hashes[i]:
                    markgot(i, i)
                elif targets.get(s) and self._piecelen(i) == self._piecelen(
                        targets[s][-1]):
                    markgot(targets[s].pop(), i)
                elif not self.have[len(hashes) - 1] and sp == hashes[-1] and (
                        i == len(hashes) - 1
                        or not self._waspre(len(hashes) - 1)):
                    markgot(len(hashes) - 1, i)
                else:
                    self.places[i] = i
                if flag.isSet():
                    return
                numchecked += 1
                statusfunc({
                    'fractionDone':
                    1 - float(self.amount_left) / self.total_length
                })
        if self.amount_left == 0:
            finished()

    def _waspre(self, piece):
        return self.storage.was_preallocated(piece * self.piece_size,
                                             self._piecelen(piece))

    def _piecelen(self, piece):
        if piece < len(self.hashes) - 1:
            return self.piece_size
        else:
            return self.total_length - piece * self.piece_size

    def get_amount_left(self):
        return self.amount_left

    def do_I_have_anything(self):
        return self.amount_left < self.total_length

    def _make_inactive(self, index):
        length = min(self.piece_size,
                     self.total_length - self.piece_size * index)
        l = []
        x = 0
        while x + self.request_size < length:
            l.append((x, self.request_size))
            x += self.request_size
        l.append((x, length - x))
        self.inactive_requests[index] = l

    def is_endgame(self):
        return self.endgame

    def get_have_list(self):
        return self.have.tostring()

    def do_I_have(self, index):
        return self.have[index]

    def do_I_have_requests(self, index):
        return not not self.inactive_requests[index]

    def new_request(self, index):
        # returns (begin, length)
        if self.inactive_requests[index] == 1:
            self._make_inactive(index)
        self.numactive[index] += 1
        rs = self.inactive_requests[index]
        r = min(rs)
        rs.remove(r)
        self.amount_inactive -= r[1]
        if self.amount_inactive == 0:
            self.endgame = True
        return r

    def piece_came_in(self, index, begin, piece):
        try:
            return self._piece_came_in(index, begin, piece)
        except IOError, e:
            self.failed('IO Error ' + str(e))
            return True
Example #15
0
 def __init__(self, storage, request_size, hashes, 
         piece_size, finished, failed, 
         statusfunc = dummy_status, flag = Event(), check_hashes = True,
         data_flunked = dummy_data_flunked):
     self.storage = storage
     self.request_size = request_size
     self.hashes = hashes
     self.piece_size = piece_size
     self.data_flunked = data_flunked
     self.total_length = storage.get_total_length()
     self.amount_left = self.total_length
     if self.total_length <= piece_size * (len(hashes) - 1):
         raise ValueError, 'bad data from tracker - total too small'
     if self.total_length > piece_size * len(hashes):
         raise ValueError, 'bad data from tracker - total too big'
     self.finished = finished
     self.failed = failed
     self.numactive = [0] * len(hashes)
     self.inactive_requests = [1] * len(hashes)
     self.amount_inactive = self.total_length
     self.endgame = False
     self.have = Bitfield(len(hashes))
     self.waschecked = [check_hashes] * len(hashes)
     self.places = {}
     self.holes = []
     if len(hashes) == 0:
         finished()
         return
     targets = {}
     total = len(hashes)
     for i in xrange(len(hashes)):
         if not self._waspre(i):
             targets.setdefault(hashes[i], []).append(i)
             total -= 1
     numchecked = 0.0
     if total and check_hashes:
         statusfunc({"activity" : 'checking existing file', 
             "fractionDone" : 0})
     def markgot(piece, pos, self = self, check_hashes = check_hashes):
         self.places[piece] = pos
         self.have[piece] = True
         self.amount_left -= self._piecelen(piece)
         self.amount_inactive -= self._piecelen(piece)
         self.inactive_requests[piece] = None
         self.waschecked[piece] = check_hashes
     lastlen = self._piecelen(len(hashes) - 1)
     for i in xrange(len(hashes)):
         if not self._waspre(i):
             self.holes.append(i)
         elif not check_hashes:
             markgot(i, i)
         else:
             sh = sha(self.storage.read(piece_size * i, lastlen))
             sp = sh.digest()
             sh.update(self.storage.read(piece_size * i + lastlen, self._piecelen(i) - lastlen))
             s = sh.digest()
             if s == hashes[i]:
                 markgot(i, i)
             elif targets.get(s) and self._piecelen(i) == self._piecelen(targets[s][-1]):
                 markgot(targets[s].pop(), i)
             elif not self.have[len(hashes) - 1] and sp == hashes[-1] and (i == len(hashes) - 1 or not self._waspre(len(hashes) - 1)):
                 markgot(len(hashes) - 1, i)
             else:
                 self.places[i] = i
             if flag.isSet():
                 return
             numchecked += 1
             statusfunc({'fractionDone': 1 - float(self.amount_left) / self.total_length})
     if self.amount_left == 0:
         finished()
Example #16
0
    def __init__(self, storage, request_size, hashes, 
            piece_size, finished, failed, 
            statusfunc = dummy_status, flag = Event(), check_hashes = True,
            data_flunked = dummy_data_flunked):
        # The Storage instance.
        self.storage = storage
        # The size of blocks to request.
        self.request_size = request_size
        # An array of SHA-1 hashes for all pieces.
        self.hashes = hashes
        # The piece size, which should be a multiple of request_size.
        self.piece_size = piece_size
        # Method to call if a piece fails SHA-1 validation.
        self.data_flunked = data_flunked
        # The total bytes to download and save.
        self.total_length = storage.get_total_length()
        # The number of bytes left to download and validate.
        self.amount_left = self.total_length
        if self.total_length <= piece_size * (len(hashes) - 1):
            raise ValueError, 'bad data from tracker - total too small'
        if self.total_length > piece_size * len(hashes):
            raise ValueError, 'bad data from tracker - total too big'
        # Callback invoked once all pieces have been downloaded and validated.
        self.finished = finished
        # Callback invoked with a string describing any error.
        self.failed = failed

        # The number of outstanding requests for blocks belonging to a piece.
        self.numactive = [0] * len(hashes)
        # If an element is 1, then the piece has not been requested from peers.
        # If an elemtent is an array, it contains blocks that have not been requested from peers.
        self.inactive_requests = [1] * len(hashes)
        # The number of bytes that have not been downloaded or requested.
        # When this reaches 0, then we enter endgame mode.
        self.amount_inactive = self.total_length
        # Whether we are in endgame mode, which
        # "sends requests for all of its missing blocks to all of its peers."
        self.endgame = False
        # The bitfield of pieces we have downloaded, but not necessarily validated.
        self.have = Bitfield(len(hashes))
        # Whether each piece has been validated by computing its SHA-1 hash.
        # If check_hashes is False, then validating preallocated segments is deferred.
        self.waschecked = [check_hashes] * len(hashes)

        # Maps each piece to what piece, or segment, it occupies on disk.
        # It may not be the right segment for the piece, and the piece may be incomplete.
        self.places = {}
        # Missing segments on disk.
        self.holes = []
        if len(hashes) == 0:
            # If no hashes, then no data to download, so trivially finished.
            finished()
            return

        # Maps each SHA-1 hash to all pieces that have that hash.
        targets = {}
        # The total number of preallocated pieces.
        total = len(hashes)
        for i in xrange(len(hashes)):
            if not self._waspre(i):
                # This piece is not preallocated.
                targets.setdefault(hashes[i], []).append(i)
                total -= 1
        numchecked = 0.0
        if total and check_hashes:
            # There is at least one preallocated piece. Must determine what pieces they are.
            statusfunc({"activity" : 'checking existing file', 
                "fractionDone" : 0})

        def markgot(piece, pos, self = self, check_hashes = check_hashes):
            # Record the position of this piece and that we have it.
            self.places[piece] = pos
            self.have[piece] = True
            # This piece has been downloaded and validated if that option is enabled.
            self.amount_left -= self._piecelen(piece)
            self.amount_inactive -= self._piecelen(piece)
            # We won't be requesting this piece from peers.
            self.inactive_requests[piece] = None
            self.waschecked[piece] = check_hashes

        # Get the length of the last piece.
        lastlen = self._piecelen(len(hashes) - 1)
        for i in xrange(len(hashes)):
            if not self._waspre(i):
                # This piece is not preallocated, i.e. it has no segment of bytes on disk.
                self.holes.append(i)
            elif not check_hashes:
                # The corresponding segment on disk is full of bytes.
                # Assume that it belongs to this piece, meaning places[i] = i.
                markgot(i, i)
            else:
                # Only get here if check_hashes = True, so we called statusfunc earlier.
                # We're trying to figure out what piece is at segment i on disk.

                # The bytes in this segment on disk could belong to any piece.
                # Compute a hash of its first lastlen bytes in case it has the last piece.
                sh = sha(self.storage.read(piece_size * i, lastlen))
                sp = sh.digest()
                # Compute a hash of all its bytes in the case that it has any other piece.
                sh.update(self.storage.read(piece_size * i + lastlen, self._piecelen(i) - lastlen))
                s = sh.digest()

                if s == hashes[i]:
                    # This is piece i, occupying its correct segment on disk.
                    markgot(i, i)
                elif targets.get(s) and self._piecelen(i) == self._piecelen(targets[s][-1]):
                    # This is not the last piece, temporarily occupying the wrong segment.
                    markgot(targets[s].pop(), i)
                elif not self.have[len(hashes) - 1] and sp == hashes[-1] and (i == len(hashes) - 1 or not self._waspre(len(hashes) - 1)):
                    # This is the last piece, temporarily occupying the wrong segment.
                    markgot(len(hashes) - 1, i)
                else:
                    # This segment has been allocated but it doesn't belong to any piece.
                    # When this piece comes in, we can write to this segment directly.
                    self.places[i] = i
                if flag.isSet():
                    return
                numchecked += 1
                statusfunc({'fractionDone': 1 - float(self.amount_left) / self.total_length})

        if self.amount_left == 0:
            # All data has been downloaded and validated.
            finished()
Example #17
0
class StorageWrapper:
    def __init__(self, storage, request_size, hashes, 
            piece_size, finished, failed, 
            statusfunc = dummy_status, flag = Event(), check_hashes = True,
            data_flunked = dummy_data_flunked):
        # The Storage instance.
        self.storage = storage
        # The size of blocks to request.
        self.request_size = request_size
        # An array of SHA-1 hashes for all pieces.
        self.hashes = hashes
        # The piece size, which should be a multiple of request_size.
        self.piece_size = piece_size
        # Method to call if a piece fails SHA-1 validation.
        self.data_flunked = data_flunked
        # The total bytes to download and save.
        self.total_length = storage.get_total_length()
        # The number of bytes left to download and validate.
        self.amount_left = self.total_length
        if self.total_length <= piece_size * (len(hashes) - 1):
            raise ValueError, 'bad data from tracker - total too small'
        if self.total_length > piece_size * len(hashes):
            raise ValueError, 'bad data from tracker - total too big'
        # Callback invoked once all pieces have been downloaded and validated.
        self.finished = finished
        # Callback invoked with a string describing any error.
        self.failed = failed

        # The number of outstanding requests for blocks belonging to a piece.
        self.numactive = [0] * len(hashes)
        # If an element is 1, then the piece has not been requested from peers.
        # If an elemtent is an array, it contains blocks that have not been requested from peers.
        self.inactive_requests = [1] * len(hashes)
        # The number of bytes that have not been downloaded or requested.
        # When this reaches 0, then we enter endgame mode.
        self.amount_inactive = self.total_length
        # Whether we are in endgame mode, which
        # "sends requests for all of its missing blocks to all of its peers."
        self.endgame = False
        # The bitfield of pieces we have downloaded, but not necessarily validated.
        self.have = Bitfield(len(hashes))
        # Whether each piece has been validated by computing its SHA-1 hash.
        # If check_hashes is False, then validating preallocated segments is deferred.
        self.waschecked = [check_hashes] * len(hashes)

        # Maps each piece to what piece, or segment, it occupies on disk.
        # It may not be the right segment for the piece, and the piece may be incomplete.
        self.places = {}
        # Missing segments on disk.
        self.holes = []
        if len(hashes) == 0:
            # If no hashes, then no data to download, so trivially finished.
            finished()
            return

        # Maps each SHA-1 hash to all pieces that have that hash.
        targets = {}
        # The total number of preallocated pieces.
        total = len(hashes)
        for i in xrange(len(hashes)):
            if not self._waspre(i):
                # This piece is not preallocated.
                targets.setdefault(hashes[i], []).append(i)
                total -= 1
        numchecked = 0.0
        if total and check_hashes:
            # There is at least one preallocated piece. Must determine what pieces they are.
            statusfunc({"activity" : 'checking existing file', 
                "fractionDone" : 0})

        def markgot(piece, pos, self = self, check_hashes = check_hashes):
            # Record the position of this piece and that we have it.
            self.places[piece] = pos
            self.have[piece] = True
            # This piece has been downloaded and validated if that option is enabled.
            self.amount_left -= self._piecelen(piece)
            self.amount_inactive -= self._piecelen(piece)
            # We won't be requesting this piece from peers.
            self.inactive_requests[piece] = None
            self.waschecked[piece] = check_hashes

        # Get the length of the last piece.
        lastlen = self._piecelen(len(hashes) - 1)
        for i in xrange(len(hashes)):
            if not self._waspre(i):
                # This piece is not preallocated, i.e. it has no segment of bytes on disk.
                self.holes.append(i)
            elif not check_hashes:
                # The corresponding segment on disk is full of bytes.
                # Assume that it belongs to this piece, meaning places[i] = i.
                markgot(i, i)
            else:
                # Only get here if check_hashes = True, so we called statusfunc earlier.
                # We're trying to figure out what piece is at segment i on disk.

                # The bytes in this segment on disk could belong to any piece.
                # Compute a hash of its first lastlen bytes in case it has the last piece.
                sh = sha(self.storage.read(piece_size * i, lastlen))
                sp = sh.digest()
                # Compute a hash of all its bytes in the case that it has any other piece.
                sh.update(self.storage.read(piece_size * i + lastlen, self._piecelen(i) - lastlen))
                s = sh.digest()

                if s == hashes[i]:
                    # This is piece i, occupying its correct segment on disk.
                    markgot(i, i)
                elif targets.get(s) and self._piecelen(i) == self._piecelen(targets[s][-1]):
                    # This is not the last piece, temporarily occupying the wrong segment.
                    markgot(targets[s].pop(), i)
                elif not self.have[len(hashes) - 1] and sp == hashes[-1] and (i == len(hashes) - 1 or not self._waspre(len(hashes) - 1)):
                    # This is the last piece, temporarily occupying the wrong segment.
                    markgot(len(hashes) - 1, i)
                else:
                    # This segment has been allocated but it doesn't belong to any piece.
                    # When this piece comes in, we can write to this segment directly.
                    self.places[i] = i
                if flag.isSet():
                    return
                numchecked += 1
                statusfunc({'fractionDone': 1 - float(self.amount_left) / self.total_length})

        if self.amount_left == 0:
            # All data has been downloaded and validated.
            finished()

    def _waspre(self, piece):
        # Returns whether all files containing this piece were preallocated.
        return self.storage.was_preallocated(piece * self.piece_size, self._piecelen(piece))

    def _piecelen(self, piece):
        # Return the length of the given piece.
        if piece < len(self.hashes) - 1:
            return self.piece_size
        else:
            return self.total_length - piece * self.piece_size

    def get_amount_left(self):
        return self.amount_left

    def do_I_have_anything(self):
        # Returns whether we've downloaded at least one piece.
        return self.amount_left < self.total_length

    def _make_inactive(self, index):
        # This assigns to length what _piecelen would return.
        length = min(self.piece_size, self.total_length - self.piece_size * index)
        # Will contain all blocks for this piece as (start byte, end byte) pairs.
        l = []
        x = 0
        while x + self.request_size < length:
            l.append((x, self.request_size))
            x += self.request_size
        l.append((x, length - x))
        # Replace the value of 1 with the array of blocks.
        self.inactive_requests[index] = l

    def is_endgame(self):
        return self.endgame

    def get_have_list(self):
        # Used when sending our bitfield to another peer.
        return self.have.tostring()

    def do_I_have(self, index):
        return self.have[index]

    def do_I_have_requests(self, index):
        # Similar to how you would convert to boolean in JavaScript,
        # this returns True if the element is 1 or a non-empty array, and
        # this returns False if the element is None or an empty array.
        return not not self.inactive_requests[index]

    def new_request(self, index):
        # returns (begin, length)
        if self.inactive_requests[index] == 1:
            # Create the blocks to request for this piece.
            self._make_inactive(index)
        # Increment count of blocks requested for this piece.
        self.numactive[index] += 1
        # Get the block with the earliest start byte.
        rs = self.inactive_requests[index]
        r = min(rs)
        rs.remove(r)
        # Deduct block length from total bytes neither downloaded nor requested.
        self.amount_inactive -= r[1]
        if self.amount_inactive == 0:
            # All bytes either downloaded or requested, so enter endgame.
            self.endgame = True
        return r

    def piece_came_in(self, index, begin, piece):
        try:
            return self._piece_came_in(index, begin, piece)
        except IOError, e:
            self.failed('IO Error ' + str(e))
            return True
from bitfield import Bitfield

bitfield = Bitfield(8, '\xee')
print 'value={0:b} complete={1}'.format(ord(bitfield.tostring()), bitfield.complete())
bitfield[3] = True
bitfield[7] = True
print 'value={0:b} complete={1}'.format(ord(bitfield.tostring()), bitfield.complete())

Example #19
0
class StorageWrapper:
    def __init__(self, storage, request_size, hashes, 
            piece_size, finished, failed, 
            statusfunc = dummy_status, flag = Event(), check_hashes = True,
            data_flunked = dummy_data_flunked):
        self.storage = storage
        self.request_size = request_size
        self.hashes = hashes
        self.piece_size = piece_size
        self.data_flunked = data_flunked
        self.total_length = storage.get_total_length()
        self.amount_left = self.total_length
        if self.total_length <= piece_size * (len(hashes) - 1):
            raise ValueError, 'bad data from tracker - total too small'
        if self.total_length > piece_size * len(hashes):
            raise ValueError, 'bad data from tracker - total too big'
        self.finished = finished
        self.failed = failed
        self.numactive = [0] * len(hashes)
        self.inactive_requests = [1] * len(hashes)
        self.amount_inactive = self.total_length
        self.endgame = False
        self.have = Bitfield(len(hashes))
        self.waschecked = [check_hashes] * len(hashes)
        self.places = {}
        self.holes = []
        if len(hashes) == 0:
            finished()
            return
        targets = {}
        total = len(hashes)
        for i in xrange(len(hashes)):
            if not self._waspre(i):
                targets.setdefault(hashes[i], []).append(i)
                total -= 1
        numchecked = 0.0
        if total and check_hashes:
            statusfunc({"activity" : 'checking existing file', 
                "fractionDone" : 0})
        def markgot(piece, pos, self = self, check_hashes = check_hashes):
            self.places[piece] = pos
            self.have[piece] = True
            self.amount_left -= self._piecelen(piece)
            self.amount_inactive -= self._piecelen(piece)
            self.inactive_requests[piece] = None
            self.waschecked[piece] = check_hashes
        lastlen = self._piecelen(len(hashes) - 1)
        for i in xrange(len(hashes)):
            if not self._waspre(i):
                self.holes.append(i)
            elif not check_hashes:
                markgot(i, i)
            else:
                sh = sha(self.storage.read(piece_size * i, lastlen))
                sp = sh.digest()
                sh.update(self.storage.read(piece_size * i + lastlen, self._piecelen(i) - lastlen))
                s = sh.digest()
                if s == hashes[i]:
                    markgot(i, i)
                elif targets.get(s) and self._piecelen(i) == self._piecelen(targets[s][-1]):
                    markgot(targets[s].pop(), i)
                elif not self.have[len(hashes) - 1] and sp == hashes[-1] and (i == len(hashes) - 1 or not self._waspre(len(hashes) - 1)):
                    markgot(len(hashes) - 1, i)
                else:
                    self.places[i] = i
                if flag.isSet():
                    return
                numchecked += 1
                statusfunc({'fractionDone': 1 - float(self.amount_left) / self.total_length})
        if self.amount_left == 0:
            finished()

    def _waspre(self, piece):
        return self.storage.was_preallocated(piece * self.piece_size, self._piecelen(piece))

    def _piecelen(self, piece):
        if piece < len(self.hashes) - 1:
            return self.piece_size
        else:
            return self.total_length - piece * self.piece_size

    def get_amount_left(self):
        return self.amount_left

    def do_I_have_anything(self):
        return self.amount_left < self.total_length

    def _make_inactive(self, index):
        length = min(self.piece_size, self.total_length - self.piece_size * index)
        l = []
        x = 0
        while x + self.request_size < length:
            l.append((x, self.request_size))
            x += self.request_size
        l.append((x, length - x))
        self.inactive_requests[index] = l

    def is_endgame(self):
        return self.endgame

    def get_have_list(self):
        return self.have.tostring()

    def do_I_have(self, index):
        return self.have[index]

    def do_I_have_requests(self, index):
        return not not self.inactive_requests[index]

    def new_request(self, index):
        # returns (begin, length)
        if self.inactive_requests[index] == 1:
            self._make_inactive(index)
        self.numactive[index] += 1
        rs = self.inactive_requests[index]
        r = min(rs)
        rs.remove(r)
        self.amount_inactive -= r[1]
        if self.amount_inactive == 0:
            self.endgame = True
        return r

    def piece_came_in(self, index, begin, piece):
        try:
            return self._piece_came_in(index, begin, piece)
        except IOError, e:
            self.failed('IO Error ' + str(e))
            return True
 def got_message(self, connection, message):
     c = self.connections[connection]
     t = message[0]
     if t == BITFIELD and c.got_anything:
         # If we are not receiving the bitfield first, close this connection.
         connection.close()
         return
     # Got at least one message from this peer.
     c.got_anything = True
     if (t in [CHOKE, UNCHOKE, INTERESTED, NOT_INTERESTED]
             and len(message) != 1):
         connection.close()
         return
     if t == CHOKE:
         # Choke messages affect downloading.
         c.download.got_choke()
     elif t == UNCHOKE:
         # Unchoke messages affect downloading.
         c.download.got_unchoke()
     elif t == INTERESTED:
         # Interested messages affect uploading.
         c.upload.got_interested()
     elif t == NOT_INTERESTED:
         # Uninterested messages affect uploading.
         c.upload.got_not_interested()
     elif t == HAVE:
         # A peer having a new piece affects downloading.
         if len(message) != 5:
             connection.close()
             return
         i = toint(message[1:])
         if i >= self.numpieces:
             connection.close()
             return
         c.download.got_have(i)
     elif t == BITFIELD:
         # A peer sending all pieces it has affects downloading.
         try:
             b = Bitfield(self.numpieces, message[1:])
         except ValueError:
             connection.close()
             return
         c.download.got_have_bitfield(b)
     elif t == REQUEST:
         # A peer requesting a block affects uploading.
         if len(message) != 13:
             connection.close()
             return
         i = toint(message[1:5])
         if i >= self.numpieces:
             connection.close()
             return
         c.upload.got_request(i, toint(message[5:9]), toint(message[9:]))
     elif t == CANCEL:
         # A peer cancelling a request for a block affects uploading.
         if len(message) != 13:
             connection.close()
             return
         i = toint(message[1:5])
         if i >= self.numpieces:
             connection.close()
             return
         c.upload.got_cancel(i, toint(message[5:9]), toint(message[9:]))
     elif t == PIECE:
         # A requested block sent to this client by a peer affects downloading.
         if len(message) <= 9:
             connection.close()
             return
         i = toint(message[1:5])
         if i >= self.numpieces:
             connection.close()
             return
         if c.download.got_piece(i, toint(message[5:9]), message[9:]):
             for co in self.connections.values():
                 co.send_have(i)
     else:
         # This is an unknown message type, so close this connection.
         connection.close()