NAME

Net::BitTorrent::Storage - Modular BitTorrent Storage Layer (v2.0.0)

SYNOPSIS

use Net::BitTorrent::Storage;

my $storage = Net::BitTorrent::Storage->new(
    base_path  => "./downloads",
    file_tree  => $metadata->{info}{'file tree'}, # From BEP 52
    piece_size => 262144
);

# Write a block (16KiB) to the async cache
# $pieces_root is the SHA-256 Merkle root from the metadata
$storage->write_block($pieces_root, $offset, $data);

# Periodically flush the cache to disk
$storage->tick(0.1);

# Force all pending data to disk (e.g. before shutdown)
$storage->explicit_flush();

DESCRIPTION

Net::BitTorrent::Storage is the central I/O manager for BitTorrent swarms. It abstracts the complexity of mapping BitTorrent "pieces" and "blocks" to actual files on disk.

In a multi-file torrent, the protocol treats all files as a single contiguous stream of bytes. This class manages the translation between this "virtual file" and the physical files on the filesystem. It is particularly important for BitTorrent v2 (BEP 52) and hybrid torrents, where file alignment and padding are strictly defined.

Key Features:

  • BitTorrent v2 (BEP 52): Uses pieces root (SHA-256 Merkle roots) to identify and verify files. Each file is verified independently.

  • Merkle Trees: Maintains internal Merkle trees for each file, allowing per-block (16KiB) verification as data arrives from the network.

  • Hybrid Mapping: Automatically handles the padding required to align v1 pieces with v2 file boundaries. This ensures that a single v1 piece never spans multiple v2 files, which simplifies verification.

  • Async Disk Cache: Buffers writes in memory and flushes them in small batches during the tick() cycle, preventing synchronous I/O from blocking the network logic.

  • Sub-range Reads: The cache is intelligent enough to serve partial reads from larger cached blocks.

METHODS

new( %params )

Creates a new Net::BitTorrent::Storage object.

my $storage = Net::BitTorrent::Storage->new(
    base_path  => './downloads',
    piece_size => 262144
);

This method initializes the storage engine. It sets up the base path for files and prepares the internal cache and file mapping structures.

Expected parameters:

base_path

The root directory where all files will be stored. This is a required parameter.

file_tree - optional

A BEP 52 file tree structure. If provided, it will be used to automatically add files to the storage.

piece_size - optional

The size of a BitTorrent piece in bytes. Required for v1 piece mapping and hybrid torrents.

pieces_v1 - optional

A concatenated string of 20-byte SHA-1 hashes for BitTorrent v1 pieces. Used for piece verification in v1 or hybrid torrents.

add_file( $rel_path, $size, [$pieces_root] )

Adds a file to the storage manager.

my $file = $storage->add_file( 'ubuntu.iso', 2147483648, $sha256_root );

This method registers a new file within the storage manager. It returns a Net::BitTorrent::Storage::File object.

Expected parameters:

$rel_path

The relative path to the file from the base_path.

$size

The size of the file in bytes.

$pieces_root - optional

The 32-byte SHA-256 Merkle root of the file (required for BEP 52).

load_file_tree( $tree )

Loads a BEP 52 file tree.

$storage->load_file_tree( $metadata->{info}{'file tree'} );

This method recursively parses a BEP 52 file tree and adds all files contained within it to the storage manager.

Expected parameters:

$tree

The hash reference representing the 'file tree' from a v2 or hybrid torrent's metadata.

get_file_by_root( $root )

Retrieves a file object by its Merkle root.

my $file = $storage->get_file_by_root( $pieces_root );

This method looks up and returns the Net::BitTorrent::Storage::File object associated with the given 32-byte Merkle root. On failure, it returns undef.

Expected parameters:

$root

The 32-byte SHA-256 Merkle root of the file.

set_piece_layer( $root, $layer_data )

Sets the piece layer data for a file.

$storage->set_piece_layer( $root, $hashes );

This method stores the piece layer (concatenated 32-byte SHA-256 hashes) for a specific file. These hashes are used to verify v2 pieces.

Expected parameters:

$root

The 32-byte SHA-256 Merkle root of the file.

$layer_data

The binary string containing the concatenated SHA-256 hashes for the file's piece layer.

get_hashes( $root, $base_layer, $index, $length )

Retrieves Merkle tree hashes.

my $hashes = $storage->get_hashes($root, $layer, $idx, $cnt);

This method fetches a sequence of hashes from the Merkle tree for BEP 52 HASH_REQUEST responses. On success, it returns an array reference of binary hashes. On failure, it returns undef.

Expected parameters:

$root

The 32-byte SHA-256 Merkle root of the file.

$base_layer

The tree layer to start from (0 is the leaves/blocks).

$index

The start index in the layer.

$length

The number of hashes to retrieve.

verify_block( $root, $index, $data )

Verifies a block against the Merkle tree (BEP 52).

my $valid = $storage->verify_block($file_root, $block_idx, $data);

This method validates a 16KiB block of data using the Merkle tree associated with the file root. It returns a boolean indicating whether the data is valid.

Expected parameters:

$root

The 32-byte SHA-256 Merkle root of the file.

$index

The block index within the file.

$data

The 16KiB block of data to verify.

verify_block_audit( $root, $index, $data, $audit_path )

Verifies a block using an audit path.

my $valid = $storage->verify_block_audit($root, $idx, $data, $audit);

This method validates data using branch nodes (audit path), useful when the full piece layer is not yet available. It returns a boolean.

Expected parameters:

$root

The 32-byte SHA-256 Merkle root of the file.

$index

The block index within the file.

$data

The 16KiB block of data.

$audit_path

An array reference of hashes representing the branch nodes needed for verification.

verify_piece_v2( $root, $index, $data )

Verifies a full v2 piece.

my $valid = $storage->verify_piece_v2($root, $idx, $data);

This method validates an entire BitTorrent v2 piece against the cached piece layers. It returns a boolean.

Expected parameters:

$root

The 32-byte SHA-256 Merkle root of the file.

$index

The piece index within the file.

$data

The full piece data.

write_block( $root, $offset, $data )

Writes data to the storage.

$storage->write_block($root, 0, $data);

This method queues a block for writing. The actual disk I/O happens asynchronously during tick().

Expected parameters:

$root

The 32-byte SHA-256 Merkle root of the target file.

$offset

The byte offset within the file.

$data

The data to write.

read_block( $root, $offset, $length )

Reads data from the storage.

my $data = $storage->read_block($root, 0, 16384);

This method reads data from the write cache if available, or falls back to reading from disk. It returns the data string on success or undef on failure.

Expected parameters:

$root

The 32-byte SHA-256 Merkle root of the target file.

$offset

The byte offset within the file.

$length

The number of bytes to read.

read_global( $offset, $length )

Reads data from the virtual global file.

my $data = $storage->read_global(0, 16384);

This method treats all files in the torrent as a single contiguous stream (BEP 03). It returns the data string.

Expected parameters:

$offset

The global byte offset.

$length

The number of bytes to read.

write_global( $offset, $data )

Writes data to the virtual global file.

$storage->write_global(0, $data);

This method writes data to the swarm's contiguous virtual file (BEP 03).

Expected parameters:

$offset

The global byte offset.

$data

The data to write.

flush( [$count] )

Flushes pending writes to disk.

$storage->flush( 16 );

This method writes a limited number of dirty cache entries to disk. It returns the number of entries successfully flushed.

Expected parameters:

$count - optional

The maximum number of cache entries to flush in this call. If omitted, all dirty entries are flushed.

explicit_flush( )

Forces all pending writes to disk.

$storage->explicit_flush();

This method forces all pending writes in the memory cache to be written to the physical disk immediately.

tick( [$delta] )

Performs periodic maintenance tasks.

$storage->tick( 0.1 );

This method performs periodic tasks like throttled cache flushing.

Expected parameters:

$delta - optional

The time elapsed since the last tick in seconds. Defaults to 0.1.

map_abs_offset( $root, $offset, $length )

Maps an offset to file segments.

my $segments = $storage->map_abs_offset( $root, $offset, $length );

This method translates an offset and length (either file-relative or global) into a list of file segments. It returns an array reference of hash references, each containing file, offset, and length.

Expected parameters:

$root

The 32-byte SHA-256 Merkle root of the file. If undef, the offset is treated as a global offset across all files.

$offset

The byte offset.

$length

The length of the range in bytes.

map_v1_piece( $index )

Maps a v1 piece to file segments.

my $segments = $storage->map_v1_piece(0);

This method determines which files and offsets a BitTorrent v1 piece covers. It handles the alignment padding required for hybrid swarms (BEP 52). It returns an array reference of segments.

Expected parameters:

$index

The v1 piece index.

write_piece_v1( $index, $data )

Writes a BitTorrent v1 piece.

$storage->write_piece_v1( 0, $data );

This method writes a full v1 piece to storage, correctly mapping it across file boundaries and padding.

Expected parameters:

$index

The v1 piece index.

$data

The full piece data.

read_piece_v1( $index )

Reads a BitTorrent v1 piece.

my $data = $storage->read_piece_v1( 0 );

This method reads a full v1 piece from storage. It returns the piece data string.

Expected parameters:

$index

The v1 piece index.

verify_piece_v1( $index, $data )

Verifies a BitTorrent v1 piece.

my $valid = $storage->verify_piece_v1( 0, $data );

This method validates a BitTorrent v1 piece against the SHA-1 hashes provided in the constructor. It returns a boolean.

Expected parameters:

$index

The v1 piece index.

$data

The piece data to verify.

map_v2_piece( $index )

Maps a v2 piece to its file.

my ($root, $rel_idx) = $storage->map_v2_piece(0);

This method identifies which file a BitTorrent v2 piece belongs to. v2 pieces are always aligned to file boundaries. It returns the 32-byte Merkle root and the relative piece index within that file.

Expected parameters:

$index

The global v2 piece index.

dump_state( )

Exports storage state.

my $state = $storage->dump_state();

This method returns the current state of the storage engine, including file statuses and internal metadata. It returns a hash reference.

load_state( $state )

Restores storage state.

$storage->load_state($state);

This method restores the storage state from a previously dumped state, allowing instant resume without re-hashing files.

Expected parameters:

$state

The hash reference containing the state data.

base_path( )

Returns the base path for storage.

my $path = $storage->base_path();

file_tree( )

Returns the configured file tree.

my $tree = $storage->file_tree();

piece_size( )

Returns the configured piece size.

my $size = $storage->piece_size();

pieces_v1( )

Returns the configured v1 piece hashes.

my $hashes = $storage->pieces_v1();

SEE ALSO

Net::BitTorrent::Storage::File, Digest::Merkle::SHA256

AUTHOR

Sanko Robinson <sanko@cpan.org>

COPYRIGHT

Copyright (C) 2008-2026 by Sanko Robinson.

This library is free software; you can redistribute it and/or modify it under the terms of the Artistic License 2.0.