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   481668 use strict;
  21         35  
  21         522  
4 21     21   88 use warnings;
  21         27  
  21         1113  
5 21     21   2547 use namespace::autoclean;
  21         83842  
  21         151  
6 21     21   2162 use autodie;
  21         23235  
  21         144  
7              
8             our $VERSION = '1.000012';
9              
10 21     21   70395 use Carp qw( confess );
  21         37  
  21         1235  
11 21     21   5835 use Math::BigInt ();
  21         111473  
  21         538  
12 21     21   2980 use MaxMind::DB::Types qw( Int );
  21         121930  
  21         1168  
13 21     21   12728 use Socket 1.87 qw( inet_pton AF_INET AF_INET6 );
  21         66427  
  21         3500  
14              
15 21     21   3358 use Moo;
  21         14809  
  21         147  
16 21     21   15474 use MooX::StrictConstructor;
  21         62766  
  21         163  
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   83338 use constant DEBUG => $ENV{MAXMIND_DB_READER_DEBUG};
  21         34  
  21         12271  
32              
33             sub BUILD {
34 22     22 0 680 my $self = shift;
35              
36 22         105 my $file = $self->file();
37              
38 22 100       524 die qq{Error opening database file "$file": The file does not exist.}
39             unless -e $file;
40              
41 21 50       79 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         92 $self->metadata;
46              
47 20         830 return;
48             }
49              
50             sub _build_data_source {
51 21     21   2721 my $self = shift;
52              
53 21         61 my $file = $self->file();
54 21         86 open my $fh, '<:raw', $file;
55              
56 21         9440 return $fh;
57             }
58              
59             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
60             sub _data_for_address {
61 203     203   307 my $self = shift;
62 203         233 my $addr = shift;
63              
64 203         427 my $pointer = $self->_find_address_in_tree($addr);
65              
66             ## no critic (Subroutines::ProhibitExplicitReturnUndef)
67 203 100       649 return undef unless $pointer;
68              
69 168         483 return $self->_get_entry_data($pointer);
70             }
71             ## use critic
72              
73             sub _find_address_in_tree {
74 203     203   256 my $self = shift;
75 203         235 my $addr = shift;
76              
77 203         437 my $is_ipv6_addr = $addr =~ /:/;
78              
79 203 100       850 my $packed_addr = inet_pton( $is_ipv6_addr ? AF_INET6 : AF_INET, $addr );
80              
81 203 50       475 die
82             "The IP address you provided ($addr) is not a valid IPv4 or IPv6 address"
83             unless defined $packed_addr;
84              
85 203         773 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     808 my $node = $self->ip_version == 6
90             && !$is_ipv6_addr ? $self->_ipv4_start_node() : 0;
91              
92 203         13442 my $bit_length = @address_bytes * 8;
93 203         544 for my $bit_num ( 0 .. $bit_length ) {
94 12867 100       25009 last if $node >= $self->node_count();
95              
96 12664         436724 my $temp_bit = 0xFF & $address_bytes[ $bit_num >> 3 ];
97 12664         14635 my $bit = 1 & ( $temp_bit >> 7 - ( $bit_num % 8 ) );
98              
99 12664         22141 my ( $left_record, $right_record ) = $self->_read_node($node);
100              
101 12664 100       17924 $node = $bit ? $right_record : $left_record;
102              
103 12664         11573 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       7578 if ( $node == $self->node_count() ) {
112 35         1101 $self->_debug_message('Record is empty')
113             if DEBUG;
114 35         75 return;
115             }
116              
117 168 50       6077 if ( $node >= $self->node_count() ) {
118 168         5868 $self->_debug_message('Record is a data pointer')
119             if DEBUG;
120 168         457 return $node;
121             }
122             }
123              
124             sub iterate_search_tree {
125 1     1 0 61 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         9553 my $depth = 1;
132 1 50       6 my $max_depth = $self->ip_version() == 4 ? 32 : 128;
133              
134 1         56 $self->_iterate_search_tree(
135             $data_callback,
136             $node_callback,
137             $node_num,
138             $ipnum,
139             $depth,
140             $max_depth,
141             );
142             }
143              
144             sub _iterate_search_tree {
145 353     353   337 my $self = shift;
146 353         287 my $data_callback = shift;
147 353         266 my $node_callback = shift;
148 353         267 my $node_num = shift;
149 353         264 my $ipnum = shift;
150 353         261 my $depth = shift;
151 353         218 my $max_depth = shift;
152              
153             ## no critic (TestingAndDebugging::ProhibitNoWarnings)
154 21     21   112 no warnings 'recursion';
  21         41  
  21         7315  
155             ## use critic
156              
157 353         760 my @records = $self->_read_node($node_num);
158 353 50       767 $node_callback->( $node_num, @records ) if $node_callback;
159              
160 353         622 for my $idx ( 0 .. 1 ) {
161 706         14208 my $value = $records[$idx];
162              
163             # We ignore empty branches of the search tree
164 706 100       1268 next if $value == $self->node_count();
165              
166 381 50       15175 my $one = $self->ip_version() == 4 ? 1 : Math::BigInt->bone();
167 381 100       20875 $ipnum = $ipnum | ( $one << ( $max_depth - $depth ) ) if $idx;
168              
169 381 100       32585 if ( $value <= $self->node_count() ) {
    50          
170 352         14841 $self->_iterate_search_tree(
171             $data_callback,
172             $node_callback,
173             $value,
174             $ipnum,
175             $depth + 1,
176             $max_depth,
177             );
178             }
179             elsif ($data_callback) {
180 0         0 $data_callback->(
181             $ipnum, $depth,
182             $self->_get_entry_data($value)
183             );
184             }
185             }
186             }
187              
188             sub _get_entry_data {
189 168     168   214 my $self = shift;
190 168         186 my $offset = shift;
191              
192 168         487 my $resolved
193             = ( $offset - $self->node_count() ) + $self->_search_tree_size();
194              
195 168 100       10126 confess q{The MaxMind DB file's search tree is corrupt}
196             if $resolved > $self->_data_source_size;
197              
198 167         3580 if (DEBUG) {
199             my $node_count = $self->node_count();
200             my $tree_size = $self->_search_tree_size();
201              
202             $self->_debug_string(
203             'Resolved data pointer',
204             "( $offset - $node_count ) + $tree_size = $resolved"
205             );
206             }
207              
208             # We only want the data from the decoder, not the offset where it was
209             # found.
210 167         512 return scalar $self->_decoder()->decode($resolved);
211             }
212              
213             sub _build_ipv4_start_node {
214 11     11   2796 my $self = shift;
215              
216 11 50       33 return 0 unless $self->ip_version == 6;
217              
218 11         447 my $node_num = 0;
219              
220 11         34 for ( 1 ... 96 ) {
221 1024         38960 ($node_num) = $self->_read_node($node_num);
222 1024 100       2308 last if $node_num >= $self->node_count();
223             }
224              
225 11         497 return $node_num;
226             }
227              
228             1;