File Coverage

blib/lib/MaxMind/DB/Reader/PP.pm
Criterion Covered Total %
statement 109 110 99.0
branch 33 42 78.5
condition 3 3 100.0
subroutine 20 20 100.0
pod 0 2 0.0
total 165 177 93.2


line stmt bran cond sub pod time code
1             package MaxMind::DB::Reader::PP;
2              
3 21     21   1013886 use strict;
  21         26  
  21         472  
4 21     21   72 use warnings;
  21         21  
  21         389  
5 21     21   2293 use namespace::autoclean;
  21         63277  
  21         125  
6 21     21   1901 use autodie;
  21         22399  
  21         127  
7              
8             our $VERSION = '1.000013';
9              
10 21     21   62973 use Carp qw( confess );
  21         29  
  21         931  
11 21     21   5497 use Math::BigInt ();
  21         94962  
  21         402  
12 21     21   2572 use MaxMind::DB::Types qw( Int );
  21         81417  
  21         932  
13 21     21   10284 use Socket 1.87 qw( inet_pton AF_INET AF_INET6 );
  21         55417  
  21         2884  
14              
15 21     21   2596 use Moo;
  21         11784  
  21         122  
16 21     21   13421 use MooX::StrictConstructor;
  21         54559  
  21         132  
17              
18             with 'MaxMind::DB::Reader::Role::Reader',
19             'MaxMind::DB::Reader::Role::NodeReader',
20             'MaxMind::DB::Reader::Role::HasDecoder',
21             'MaxMind::DB::Role::Debugs';
22              
23             has _ipv4_start_node => (
24             is => 'ro',
25             isa => Int,
26             init_arg => undef,
27             lazy => 1,
28             builder => '_build_ipv4_start_node',
29             );
30              
31 21     21   76345 use constant DEBUG => $ENV{MAXMIND_DB_READER_DEBUG};
  21         31  
  21         10480  
32              
33             sub BUILD {
34 22     22 0 704 my $self = shift;
35              
36 22         79 my $file = $self->file();
37              
38 22 100       659 die qq{Error opening database file "$file": The file does not exist.}
39             unless -e $file;
40              
41 21 50       67 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 21         86 $self->metadata;
46              
47 20         716 return;
48             }
49              
50             sub _build_data_source {
51 21     21   1629 my $self = shift;
52              
53 21         54 my $file = $self->file();
54 21         75 open my $fh, '<:raw', $file;
55              
56 21         7193 return $fh;
57             }
58              
59             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
60             sub _data_for_address {
61 203     203   174 my $self = shift;
62 203         191 my $addr = shift;
63              
64 203         265 my $pointer = $self->_find_address_in_tree($addr);
65              
66             ## no critic (Subroutines::ProhibitExplicitReturnUndef)
67 203 100       451 return undef unless $pointer;
68              
69 168         323 return $self->_get_entry_data($pointer);
70             }
71             ## use critic
72              
73             sub _find_address_in_tree {
74 203     203   168 my $self = shift;
75 203         164 my $addr = shift;
76              
77 203         250 my $is_ipv6_addr = $addr =~ /:/;
78              
79 203 100       500 my $packed_addr = inet_pton( $is_ipv6_addr ? AF_INET6 : AF_INET, $addr );
80              
81 203 50       343 die
82             "The IP address you provided ($addr) is not a valid IPv4 or IPv6 address"
83             unless defined $packed_addr;
84              
85 203         530 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 203 100 100     574 my $node = $self->ip_version == 6
90             && !$is_ipv6_addr ? $self->_ipv4_start_node() : 0;
91              
92 203         10566 my $bit_length = @address_bytes * 8;
93 203         348 for my $bit_num ( 0 .. $bit_length ) {
94 12867 100       21930 last if $node >= $self->node_count();
95              
96 12664         396098 my $temp_bit = 0xFF & $address_bytes[ $bit_num >> 3 ];
97 12664         13348 my $bit = 1 & ( $temp_bit >> 7 - ( $bit_num % 8 ) );
98              
99 12664         21369 my ( $left_record, $right_record ) = $self->_read_node($node);
100              
101 12664 100       16407 $node = $bit ? $right_record : $left_record;
102              
103 12664         13044 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 203 100       6640 if ( $node == $self->node_count() ) {
112 35         1067 $self->_debug_message('Record is empty')
113             if DEBUG;
114 35         62 return;
115             }
116              
117 168 50       5183 if ( $node >= $self->node_count() ) {
118 168         5059 $self->_debug_message('Record is a data pointer')
119             if DEBUG;
120 168         384 return $node;
121             }
122             }
123              
124             sub iterate_search_tree {
125 1     1 0 66 my $self = shift;
126 1         2 my $data_callback = shift;
127 1         2 my $node_callback = shift;
128              
129 1         1 my $node_num = 0;
130 1 50       5 my $ipnum = $self->ip_version() == 4 ? 0 : Math::BigInt->bzero();
131 1         10264 my $depth = 1;
132 1 50       7 my $max_depth = $self->ip_version() == 4 ? 32 : 128;
133              
134 1         60 $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)
145             sub _iterate_search_tree {
146 353     353   327 my $self = shift;
147 353         242 my $data_callback = shift;
148 353         204 my $node_callback = shift;
149 353         225 my $node_num = shift;
150 353         235 my $ipnum = shift;
151 353         221 my $depth = shift;
152 353         196 my $max_depth = shift;
153              
154             ## no critic (TestingAndDebugging::ProhibitNoWarnings)
155 21     21   101 no warnings 'recursion';
  21         25  
  21         6457  
156             ## use critic
157              
158 353         633 my @records = $self->_read_node($node_num);
159 353 50       624 $node_callback->( $node_num, @records ) if $node_callback;
160              
161 353         519 for my $idx ( 0 .. 1 ) {
162 706         11847 my $value = $records[$idx];
163              
164             # We ignore empty branches of the search tree
165 706 100       1134 next if $value == $self->node_count();
166              
167 381 50       12133 my $one = $self->ip_version() == 4 ? 1 : Math::BigInt->bone();
168 381 100       17414 $ipnum = $ipnum | ( $one << ( $max_depth - $depth ) ) if $idx;
169              
170 381 100       26042 if ( $value <= $self->node_count() ) {
    50          
171 352         12162 $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 0         0 $data_callback->(
182             $ipnum, $depth,
183             $self->_get_entry_data($value)
184             );
185             }
186             }
187             }
188             ## use critic
189              
190             sub _get_entry_data {
191 168     168   160 my $self = shift;
192 168         179 my $offset = shift;
193              
194 168         369 my $resolved
195             = ( $offset - $self->node_count() ) + $self->_search_tree_size();
196              
197 168 100       8658 confess q{The MaxMind DB file's search tree is corrupt}
198             if $resolved > $self->_data_source_size;
199              
200 167         3120 if (DEBUG) {
201             my $node_count = $self->node_count();
202             my $tree_size = $self->_search_tree_size();
203              
204             $self->_debug_string(
205             'Resolved data pointer',
206             "( $offset - $node_count ) + $tree_size = $resolved"
207             );
208             }
209              
210             # We only want the data from the decoder, not the offset where it was
211             # found.
212 167         379 return scalar $self->_decoder()->decode($resolved);
213             }
214              
215             sub _build_ipv4_start_node {
216 11     11   2435 my $self = shift;
217              
218 11 50       33 return 0 unless $self->ip_version == 6;
219              
220 11         366 my $node_num = 0;
221              
222 11         40 for ( 1 ... 96 ) {
223 1024         32498 ($node_num) = $self->_read_node($node_num);
224 1024 100       1955 last if $node_num >= $self->node_count();
225             }
226              
227 11         486 return $node_num;
228             }
229              
230             1;