Documentation for Hasher
Module
module
torrentfile.
hasher
Piece/File Hashers for Bittorrent meta file contents.
Classes
Hasher
— Piece hasher for Bittorrent V1 files.HasherV2
— Calculate the root hash and piece layers for file contents.HasherHybrid
— Calculate root and piece hashes for creating hybrid torrent file.
Functions
merkle_root
(
blocks
)
— Calculate the merkle root for a seq of sha256 hash digests.
torrentfile.hasher.merkle_root(blocks)
Source code in torrentfile\hasher.py
def merkle_root(blocks):
"""Calculate the merkle root for a seq of sha256 hash digests."""
while len(blocks) > 1:
blocks = [sha256(x + y).digest() for x, y in zip(*[iter(blocks)] * 2)]
return blocks[0]
torrentfile.hasher.Hasher
__init__(self, paths, piece_length)
special
Source code in torrentfile\hasher.py
def __init__(self, paths, piece_length):
"""Generate hashes of piece length data from filelist contents."""
self.piece_length = piece_length
self.paths = paths
self.total = sum([os.path.getsize(i) for i in self.paths])
self.index = 0
self.current = open(self.paths[0], "rb")
hashlog.debug(
"Hashing v1 torrent file. Size: %s Piece Length: %s",
humanize_bytes(self.total),
humanize_bytes(self.piece_length),
)
__iter__(self)
special
Source code in torrentfile\hasher.py
def __iter__(self):
"""Iterate through feed pieces.
Returns
-------
self : `iterator`
Iterator for leaves/hash pieces.
"""
return self
__next__(self)
special
Source code in torrentfile\hasher.py
def __next__(self):
"""Generate piece-length pieces of data from input file list."""
while True:
piece = bytearray(self.piece_length)
size = self.current.readinto(piece)
if size == 0:
if not self.next_file():
raise StopIteration
elif size < self.piece_length:
return self._handle_partial(piece[:size])
else:
return sha1(piece).digest() # nosec
next_file(self)
Source code in torrentfile\hasher.py
def next_file(self):
"""Seemlessly transition to next file in file list."""
self.index += 1
if self.index < len(self.paths):
self.current.close()
self.current = open(self.paths[self.index], "rb")
return True
return False
torrentfile.hasher.HasherV2
__init__(self, path, piece_length)
special
Source code in torrentfile\hasher.py
def __init__(self, path, piece_length):
"""Calculate and store hash information for specific file."""
self.path = path
self.root = None
self.piece_layer = None
self.layer_hashes = []
self.piece_length = piece_length
self.num_blocks = piece_length // BLOCK_SIZE
hashlog.debug(
"Hashing partial v2 torrent file. Piece Length: %s Path: %s",
humanize_bytes(self.piece_length),
str(self.path),
)
with open(self.path, "rb") as fd:
self.process_file(fd)
process_file(self, fd)
Source code in torrentfile\hasher.py
def process_file(self, fd):
"""Calculate hashes over 16KiB chuncks of file content.
Parameters
----------
fd : `str`
Opened file in read mode.
"""
while True:
total = 0
blocks = []
leaf = bytearray(BLOCK_SIZE)
# generate leaves of merkle tree
for _ in range(self.num_blocks):
size = fd.readinto(leaf)
total += size
if not size:
break
blocks.append(sha256(leaf[:size]).digest())
# blocks is empty mean eof
if not blocks:
break
if len(blocks) != self.num_blocks:
# when size of file doesn't fill the last block
if not self.layer_hashes:
# when the there is only one block for file
next_pow_2 = 1 << int(math.log2(total) + 1)
remaining = ((next_pow_2 - total) // BLOCK_SIZE) + 1
else:
# when the file contains multiple pieces
remaining = self.num_blocks - size
# pad the the rest with zeroes to fill remaining space.
padding = [bytes(HASH_SIZE) for _ in range(remaining)]
blocks.extend(padding)
# calculate the root hash for the merkle tree up to piece-length
layer_hash = merkle_root(blocks)
self.layer_hashes.append(layer_hash)
self._calculate_root()
torrentfile.hasher.HasherHybrid
__init__(self, path, piece_length)
special
Source code in torrentfile\hasher.py
def __init__(self, path, piece_length):
"""Construct Hasher class instances for each file in torrent."""
self.path = path
self.piece_length = piece_length
self.pieces = []
self.layer_hashes = []
self.piece_layer = None
self.root = None
self.padding_piece = None
self.padding_file = None
self.amount = piece_length // BLOCK_SIZE
hashlog.debug(
"Hashing partial Hybrid torrent file. Piece Length: %s Path: %s",
humanize_bytes(self.piece_length),
str(self.path),
)
with open(path, "rb") as data:
self._process_file(data)