=pod

=encoding utf-8

=head1 NAME

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

=head1 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();

=head1 DESCRIPTION

C<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
B<BitTorrent v2 (BEP 52)> and hybrid torrents, where file alignment and padding are strictly defined.

=head2 Key Features:

=over

=item * B<BitTorrent v2 (BEP 52)>: Uses C<pieces root> (SHA-256 Merkle roots) to identify and verify files. Each file is verified independently.

=item * B<Merkle Trees>: Maintains internal Merkle trees for each file, allowing per-block (16KiB) verification as data arrives from the network.

=item * B<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.

=item * B<Async Disk Cache>: Buffers writes in memory and flushes them in small batches during the C<tick()>
cycle, preventing synchronous I/O from blocking the network logic.

=item * B<Sub-range Reads>: The cache is intelligent enough to serve partial reads from larger cached blocks.

=back

=head1 METHODS

=head2 C<new( %params )>

Creates a new C<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:

=over

=item C<base_path>

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

=item C<file_tree> - optional

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

=item C<piece_size> - optional

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

=item C<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.

=back

=head2 C<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 L<Net::BitTorrent::Storage::File> object.

Expected parameters:

=over

=item C<$rel_path>

The relative path to the file from the C<base_path>.

=item C<$size>

The size of the file in bytes.

=item C<$pieces_root> - optional

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

=back

=head2 C<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:

=over

=item C<$tree>

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

=back

=head2 C<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 L<Net::BitTorrent::Storage::File> object associated with the given 32-byte Merkle
root. On failure, it returns C<undef>.

Expected parameters:

=over

=item C<$root>

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

=back

=head2 C<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:

=over

=item C<$root>

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

=item C<$layer_data>

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

=back

=head2 C<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 C<HASH_REQUEST> responses. On success, it
returns an array reference of binary hashes. On failure, it returns C<undef>.

Expected parameters:

=over

=item C<$root>

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

=item C<$base_layer>

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

=item C<$index>

The start index in the layer.

=item C<$length>

The number of hashes to retrieve.

=back

=head2 C<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:

=over

=item C<$root>

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

=item C<$index>

The block index within the file.

=item C<$data>

The 16KiB block of data to verify.

=back

=head2 C<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:

=over

=item C<$root>

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

=item C<$index>

The block index within the file.

=item C<$data>

The 16KiB block of data.

=item C<$audit_path>

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

=back

=head2 C<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:

=over

=item C<$root>

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

=item C<$index>

The piece index within the file.

=item C<$data>

The full piece data.

=back

=head2 C<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 C<tick()>.

Expected parameters:

=over

=item C<$root>

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

=item C<$offset>

The byte offset within the file.

=item C<$data>

The data to write.

=back

=head2 C<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 C<undef> on failure.

Expected parameters:

=over

=item C<$root>

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

=item C<$offset>

The byte offset within the file.

=item C<$length>

The number of bytes to read.

=back

=head2 C<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:

=over

=item C<$offset>

The global byte offset.

=item C<$length>

The number of bytes to read.

=back

=head2 C<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:

=over

=item C<$offset>

The global byte offset.

=item C<$data>

The data to write.

=back

=head2 C<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:

=over

=item C<$count> - optional

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

=back

=head2 C<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.

=head2 C<tick( [$delta] )>

Performs periodic maintenance tasks.

    $storage->tick( 0.1 );

This method performs periodic tasks like throttled cache flushing.

Expected parameters:

=over

=item C<$delta> - optional

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

=back

=head2 C<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 C<file>, C<offset>, and C<length>.

Expected parameters:

=over

=item C<$root>

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

=item C<$offset>

The byte offset.

=item C<$length>

The length of the range in bytes.

=back

=head2 C<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:

=over

=item C<$index>

The v1 piece index.

=back

=head2 C<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:

=over

=item C<$index>

The v1 piece index.

=item C<$data>

The full piece data.

=back

=head2 C<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:

=over

=item C<$index>

The v1 piece index.

=back

=head2 C<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:

=over

=item C<$index>

The v1 piece index.

=item C<$data>

The piece data to verify.

=back

=head2 C<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:

=over

=item C<$index>

The global v2 piece index.

=back

=head2 C<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.

=head2 C<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:

=over

=item C<$state>

The hash reference containing the state data.

=back

=head2 C<base_path( )>

Returns the base path for storage.

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

=head2 C<file_tree( )>

Returns the configured file tree.

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

=head2 C<piece_size( )>

Returns the configured piece size.

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

=head2 C<pieces_v1( )>

Returns the configured v1 piece hashes.

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

=head1 SEE ALSO

L<Net::BitTorrent::Storage::File>, L<Digest::Merkle::SHA256>

=head1 AUTHOR

Sanko Robinson E<lt>sanko@cpan.orgE<gt>

=head1 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.

=cut
