1package MaxMind::DB::Reader::PP;
2
3use strict;
4use warnings;
5use namespace::autoclean;
6use autodie;
7
8our $VERSION = '1.000014';
9
10use Carp qw( confess );
11use Math::BigInt ();
12use MaxMind::DB::Types qw( Int );
13use Socket 1.87 qw( inet_pton AF_INET AF_INET6 );
14
15use Moo;
16use MooX::StrictConstructor;
17
18with 'MaxMind::DB::Reader::Role::Reader',
19    'MaxMind::DB::Reader::Role::NodeReader',
20    'MaxMind::DB::Reader::Role::HasDecoder',
21    'MaxMind::DB::Role::Debugs';
22
23has _ipv4_start_node => (
24    is       => 'ro',
25    isa      => Int,
26    init_arg => undef,
27    lazy     => 1,
28    builder  => '_build_ipv4_start_node',
29);
30
31use constant DEBUG => $ENV{MAXMIND_DB_READER_DEBUG};
32
33sub BUILD {
34    my $self = shift;
35
36    my $file = $self->file;
37
38    die qq{Error opening database file "$file": The file does not exist.}
39        unless -e $file;
40
41    die qq{Error opening database file "$file": The file cannot be read.}
42        unless -r _;
43
44    # Build the metadata right away to ensure file's validity
45    $self->metadata;
46
47    return;
48}
49
50sub _build_data_source {
51    my $self = shift;
52
53    my $file = $self->file;
54    open my $fh, '<:raw', $file;
55
56    return $fh;
57}
58
59## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
60sub _data_for_address {
61    my $self = shift;
62    my $addr = shift;
63
64    my $pointer = $self->_find_address_in_tree($addr);
65
66    ## no critic (Subroutines::ProhibitExplicitReturnUndef)
67    return undef unless $pointer;
68
69    return $self->_get_entry_data($pointer);
70}
71## use critic
72
73sub _find_address_in_tree {
74    my $self = shift;
75    my $addr = shift;
76
77    my $is_ipv6_addr = $addr =~ /:/;
78
79    my $packed_addr = inet_pton( $is_ipv6_addr ? AF_INET6 : AF_INET, $addr );
80
81    die
82        "The IP address you provided ($addr) is not a valid IPv4 or IPv6 address"
83        unless defined $packed_addr;
84
85    my @address_bytes = unpack( 'C*', $packed_addr );
86
87    # The first node of the tree is always node 0, at the beginning of the
88    # value
89    my $node = $self->ip_version == 6
90        && !$is_ipv6_addr ? $self->_ipv4_start_node : 0;
91
92    my $bit_length = @address_bytes * 8;
93    for my $bit_num ( 0 .. $bit_length ) {
94        last if $node >= $self->node_count;
95
96        my $temp_bit = 0xFF & $address_bytes[ $bit_num >> 3 ];
97        my $bit      = 1 & ( $temp_bit >> 7 - ( $bit_num % 8 ) );
98
99        my ( $left_record, $right_record ) = $self->_read_node($node);
100
101        $node = $bit ? $right_record : $left_record;
102
103        if (DEBUG) {
104            $self->_debug_string( 'Bit #',        $bit_length - $bit_num );
105            $self->_debug_string( 'Bit value',    $bit );
106            $self->_debug_string( 'Record',       $bit ? 'right' : 'left' );
107            $self->_debug_string( 'Record value', $node );
108        }
109    }
110
111    if ( $node == $self->node_count ) {
112        $self->_debug_message('Record is empty')
113            if DEBUG;
114        return;
115    }
116
117    if ( $node >= $self->node_count ) {
118        $self->_debug_message('Record is a data pointer')
119            if DEBUG;
120        return $node;
121    }
122}
123
124sub iterate_search_tree {
125    my $self          = shift;
126    my $data_callback = shift;
127    my $node_callback = shift;
128
129    my $node_num  = 0;
130    my $ipnum     = $self->ip_version() == 4 ? 0 : Math::BigInt->bzero();
131    my $depth     = 1;
132    my $max_depth = $self->ip_version() == 4 ? 32 : 128;
133
134    $self->_iterate_search_tree(
135        $data_callback,
136        $node_callback,
137        $node_num,
138        $ipnum,
139        $depth,
140        $max_depth,
141    );
142}
143
144## no critic (Subroutines::ProhibitManyArgs)
145sub _iterate_search_tree {
146    my $self          = shift;
147    my $data_callback = shift;
148    my $node_callback = shift;
149    my $node_num      = shift;
150    my $ipnum         = shift;
151    my $depth         = shift;
152    my $max_depth     = shift;
153
154    ## no critic (TestingAndDebugging::ProhibitNoWarnings)
155    no warnings 'recursion';
156    ## use critic
157
158    my @records = $self->_read_node($node_num);
159    $node_callback->( $node_num, @records ) if $node_callback;
160
161    for my $idx ( 0 .. 1 ) {
162        my $value = $records[$idx];
163
164        # We ignore empty branches of the search tree
165        next if $value == $self->node_count();
166
167        my $one = $self->ip_version() == 4 ? 1 : Math::BigInt->bone();
168        $ipnum = $ipnum | ( $one << ( $max_depth - $depth ) ) if $idx;
169
170        if ( $value <= $self->node_count() ) {
171            $self->_iterate_search_tree(
172                $data_callback,
173                $node_callback,
174                $value,
175                $ipnum,
176                $depth + 1,
177                $max_depth,
178            );
179        }
180        elsif ($data_callback) {
181            $data_callback->(
182                $ipnum, $depth,
183                $self->_get_entry_data($value)
184            );
185        }
186    }
187}
188## use critic
189
190sub _get_entry_data {
191    my $self   = shift;
192    my $offset = shift;
193
194    my $resolved = ( $offset - $self->node_count ) + $self->_search_tree_size;
195
196    confess q{The MaxMind DB file's search tree is corrupt}
197        if $resolved > $self->_data_source_size;
198
199    if (DEBUG) {
200        my $node_count = $self->node_count;
201        my $tree_size  = $self->_search_tree_size;
202
203        $self->_debug_string(
204            'Resolved data pointer',
205            "( $offset - $node_count ) + $tree_size = $resolved"
206        );
207    }
208
209    # We only want the data from the decoder, not the offset where it was
210    # found.
211    return scalar $self->_decoder->decode($resolved);
212}
213
214sub _build_ipv4_start_node {
215    my $self = shift;
216
217    return 0 unless $self->ip_version == 6;
218
219    my $node_num = 0;
220
221    for ( 1 ... 96 ) {
222        ($node_num) = $self->_read_node($node_num);
223        last if $node_num >= $self->node_count;
224    }
225
226    return $node_num;
227}
228
2291;
230