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