The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package MaxMind::DB::Reader::Role::Reader;
$MaxMind::DB::Reader::Role::Reader::VERSION = '0.050005';
use strict;
use warnings;
use namespace::autoclean;
use autodie;

use Data::Validate::IP 0.16
    qw( is_ipv4 is_ipv6 is_private_ipv4 is_private_ipv6 );
use Math::Int128 qw( uint128 );
use Net::Works::Address 0.12;

use Moo::Role;

requires qw(
    _build_metadata
    _data_for_address
    _get_entry_data
    _read_node
);

use constant DEBUG => $ENV{MAXMIND_DB_READER_DEBUG};

sub record_for_address {
    my $self = shift;
    my $addr = shift;

    die 'You must provide an IP address to look up'
        unless defined $addr and length $addr;

    die
        "The IP address you provided ($addr) is not a valid IPv4 or IPv6 address"
        unless is_ipv4($addr) || is_ipv6($addr);

    die "The IP address you provided ($addr) is not a public IP address"
        if is_private_ipv4($addr) || is_private_ipv6($addr);

    return $self->_data_for_address($addr);
}

sub iterate_search_tree {
    my $self          = shift;
    my $data_callback = shift;
    my $node_callback = shift;

    my $node_num  = 0;
    my $ipnum     = $self->ip_version() == 4 ? 0 : uint128(0);
    my $depth     = 1;
    my $max_depth = $self->ip_version() == 4 ? 32 : 128;

    $self->_iterate_search_tree(
        $data_callback,
        $node_callback,
        $node_num,
        $ipnum,
        $depth,
        $max_depth,
    );
}

sub _iterate_search_tree {
    my $self          = shift;
    my $data_callback = shift;
    my $node_callback = shift;
    my $node_num      = shift;
    my $ipnum         = shift;
    my $depth         = shift;
    my $max_depth     = shift;

    no warnings 'recursion';

    my @records = $self->_read_node($node_num);
    $node_callback->( $node_num, @records ) if $node_callback;

    for my $idx ( 0..1 ) {
        my $value = $records[$idx];

        # We ignore empty branches of the search tree
        next if $value == $self->node_count();

        my $one = $self->ip_version() == 4 ? 1 : uint128(1);
        $ipnum = $ipnum | ( $one << ( $max_depth - $depth ) ) if $idx;

        if ( $value <= $self->node_count() ) {
            $self->_iterate_search_tree(
                $data_callback,
                $node_callback,
                $value,
                $ipnum,
                $depth + 1,
                $max_depth,
            );
        }
        elsif ($data_callback) {
            $data_callback->(
                $ipnum, $depth,
                $self->_get_entry_data($value)
            );
        }
    }
}

around _build_metadata => sub {
    my $orig = shift;
    my $self = shift;

    return $self->$orig(@_) unless DEBUG;

    my $metadata = $self->$orig(@_);

    $metadata->debug_dump();

    return $metadata;
};

1;