File Coverage

blib/lib/MaxMind/DB/Reader/Decoder.pm
Criterion Covered Total %
statement 183 183 100.0
branch 48 50 96.0
condition n/a
subroutine 32 32 100.0
pod 0 1 0.0
total 263 266 98.8


line stmt bran cond sub pod time code
1             package MaxMind::DB::Reader::Decoder;
2              
3 21     21   938496 use strict;
  21         114  
  21         616  
4 21     21   110 use warnings;
  21         40  
  21         557  
5 21     21   6879 use namespace::autoclean;
  21         191506  
  21         106  
6 21     21   1351 use autodie;
  21         43  
  21         164  
7              
8             our $VERSION = '1.000014';
9              
10 21     21   114814 use Carp qw( confess );
  21         51  
  21         1356  
11 21     21   9988 use Data::IEEE754 qw( unpack_double_be unpack_float_be );
  21         24972  
  21         1231  
12 21     21   9004 use Encode ();
  21         174047  
  21         514  
13 21     21   4459 use Math::BigInt qw();
  21         108830  
  21         692  
14 21     21   9258 use MaxMind::DB::Common 0.040001 qw( %TypeNumToName );
  21         9516  
  21         2954  
15 21     21   9709 use MaxMind::DB::Reader::Data::Container;
  21         64  
  21         654  
16 21     21   8358 use MaxMind::DB::Reader::Data::EndMarker;
  21         62  
  21         704  
17 21     21   6382 use MaxMind::DB::Types qw( Int );
  21         143067  
  21         1250  
18              
19 21     21   8127 use Moo;
  21         119550  
  21         125  
20 21     21   32648 use MooX::StrictConstructor;
  21         203287  
  21         154  
21              
22             with 'MaxMind::DB::Role::Debugs', 'MaxMind::DB::Reader::Role::Sysreader';
23              
24 21     21   278695 use constant DEBUG => $ENV{MAXMIND_DB_DECODER_DEBUG};
  21         59  
  21         1582  
25              
26             # This is a constant so that outside of testing any references to it can be
27             # optimised away by the compiler.
28 21     21   144 use constant POINTER_TEST_HACK => $ENV{MAXMIND_DB_POINTER_TEST_HACK};
  21         61  
  21         39364  
29              
30             binmode STDERR, ':utf8'
31             if DEBUG;
32              
33             has _pointer_base => (
34             is => 'ro',
35             isa => Int,
36             init_arg => 'pointer_base',
37             default => 0,
38             );
39              
40             sub decode {
41 1240     1240 0 678196 my $self = shift;
42 1240         2006 my $offset = shift;
43              
44 1240 100       3116 confess 'You must provide an offset to decode from when calling ->decode'
45             unless defined $offset;
46              
47 1239 100       25115 confess
48             q{The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)}
49             if $offset >= $self->_data_source_size;
50              
51 1238         10745 if (DEBUG) {
52             $self->_debug_newline;
53             $self->_debug_string( 'Offset', $offset );
54             }
55              
56 1238         1940 my $ctrl_byte;
57 1238         4304 $self->_read( \$ctrl_byte, $offset, 1 );
58 1238         2208 $offset++;
59              
60 1238         2040 $self->_debug_binary( 'Control byte', $ctrl_byte )
61             if DEBUG;
62              
63 1238         3355 $ctrl_byte = unpack( C => $ctrl_byte );
64              
65             # The type is encoded in the first 3 bits of the byte.
66 1238         3720 my $type = $TypeNumToName{ $ctrl_byte >> 5 };
67              
68 1238         1962 $self->_debug_string( 'Type', $type )
69             if DEBUG;
70              
71             # Pointers are a special case, we don't read the next $size bytes, we use
72             # the size to determine the length of the pointer and then follow it.
73 1238 100       2872 if ( $type eq 'pointer' ) {
74 38         117 my ( $pointer, $new_offset )
75             = $self->_decode_pointer( $ctrl_byte, $offset );
76              
77 38         75 return $pointer if POINTER_TEST_HACK;
78              
79 28         93 my $value = $self->decode($pointer);
80             return wantarray
81 27 100       1399 ? ( $value, $new_offset )
82             : $value;
83             }
84              
85 1200 100       2455 if ( $type eq 'extended' ) {
86 108         208 my $next_byte;
87 108         409 $self->_read( \$next_byte, $offset, 1 );
88              
89 108         195 $self->_debug_binary( 'Next byte', $next_byte )
90             if DEBUG;
91              
92 108         317 my $type_num = unpack( C => $next_byte ) + 7;
93 108 50       290 confess
94             "Something went horribly wrong in the decoder. An extended type resolved to a type number < 8 ($type_num)"
95             if $type_num < 8;
96              
97 108         302 $type = $TypeNumToName{$type_num};
98 108         190 $offset++;
99             }
100              
101 1200         3569 ( my $size, $offset )
102             = $self->_size_from_ctrl_byte( $ctrl_byte, $offset );
103              
104 1200         1977 $self->_debug_string( 'Size', $size )
105             if DEBUG;
106              
107             # The map and array types are special cases, since we don't read the next
108             # $size bytes. For all other types, we do.
109 1200 100       3009 return $self->_decode_map( $size, $offset )
110             if $type eq 'map';
111              
112 979 100       2068 return $self->_decode_array( $size, $offset )
113             if $type eq 'array';
114              
115 955 100       2022 return $self->_decode_boolean( $size, $offset )
116             if $type eq 'boolean';
117              
118 953         1457 my $buffer;
119 953 100       3612 $self->_read( \$buffer, $offset, $size )
120             if $size;
121              
122 953         1507 $self->_debug_binary( 'Buffer', $buffer )
123             if DEBUG;
124              
125 953 100       3679 my $method = '_decode_' . ( $type =~ /^uint/ ? 'uint' : $type );
126             return wantarray
127 953 100       3937 ? ( $self->$method( $buffer, $size ), $offset + $size )
128             : $self->$method( $buffer, $size );
129             }
130              
131             my %pointer_value_offset = (
132             1 => 0,
133             2 => 2**11,
134             3 => 2**19 + 2**11,
135             4 => 0,
136             );
137              
138             sub _decode_pointer {
139 38     38   72 my $self = shift;
140 38         73 my $ctrl_byte = shift;
141 38         52 my $offset = shift;
142              
143 38         83 my $pointer_size = ( ( $ctrl_byte >> 3 ) & 0b00000011 ) + 1;
144              
145 38         71 $self->_debug_string( 'Pointer size', $pointer_size )
146             if DEBUG;
147              
148 38         58 my $buffer;
149 38         127 $self->_read( \$buffer, $offset, $pointer_size );
150              
151 38         59 $self->_debug_binary( 'Buffer', $buffer )
152             if DEBUG;
153              
154 38 100       182 my $packed
155             = $pointer_size == 4
156             ? $buffer
157             : ( pack( C => $ctrl_byte & 0b00000111 ) ) . $buffer;
158              
159 38         126 $packed = $self->_zero_pad_left( $packed, 4 );
160              
161 38         64 $self->_debug_binary( 'Packed pointer', $packed )
162             if DEBUG;
163              
164 38         152 my $pointer = unpack( 'N' => $packed ) + $self->_pointer_base;
165 38         93 $pointer += $pointer_value_offset{$pointer_size};
166              
167 38         55 $self->_debug_string( 'Pointer to', $pointer )
168             if DEBUG;
169              
170 38         123 return ( $pointer, $offset + $pointer_size );
171             }
172              
173             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
174             sub _decode_utf8_string {
175 728     728   1232 my $self = shift;
176 728         1190 my $buffer = shift;
177 728         1207 my $size = shift;
178              
179 728 100       1604 return q{} if $size == 0;
180              
181             ## no critic (Subroutines::ProhibitCallsToUnexportedSubs)
182 727         2378 return Encode::decode( 'utf-8', $buffer, Encode::FB_CROAK );
183             }
184              
185             sub _decode_double {
186 17     17   30 my $self = shift;
187 17         26 my $buffer = shift;
188 17         24 my $size = shift;
189              
190 17         52 $self->_verify_size( 8, $size );
191 16         52 return unpack_double_be($buffer);
192             }
193              
194             sub _decode_float {
195 18     18   30 my $self = shift;
196 18         31 my $buffer = shift;
197 18         24 my $size = shift;
198              
199 18         56 $self->_verify_size( 4, $size );
200 18         54 return unpack_float_be($buffer);
201             }
202              
203             sub _decode_bytes {
204 11     11   19 my $self = shift;
205 11         18 my $buffer = shift;
206 11         18 my $size = shift;
207              
208 11 100       27 return q{} if $size == 0;
209              
210 10         32 return $buffer;
211             }
212              
213             sub _decode_map {
214 221     221   408 my $self = shift;
215 221         368 my $size = shift;
216 221         372 my $offset = shift;
217              
218 221         370 $self->_debug_string( 'Map size', $size )
219             if DEBUG;
220              
221 221         446 my %map;
222 221         715 for ( 1 .. $size ) {
223 421         1104 ( my $key, $offset ) = $self->decode($offset);
224 421         25088 ( my $val, $offset ) = $self->decode($offset);
225              
226 419         13096 if (DEBUG) {
227             $self->_debug_string( "Key $_", $key );
228             $self->_debug_string( "Value $_", $val );
229             }
230              
231 419         1885 $map{$key} = $val;
232             }
233              
234 219         358 $self->_debug_structure( 'Decoded map', \%map )
235             if DEBUG;
236              
237 219 100       2430 return wantarray ? ( \%map, $offset ) : \%map;
238             }
239              
240             sub _decode_int32 {
241 12     12   20 my $self = shift;
242 12         20 my $buffer = shift;
243 12         22 my $size = shift;
244              
245 12 100       27 return 0 if $size == 0;
246              
247 11         47 return unpack( 'N!' => $self->_zero_pad_left( $buffer, 4 ) );
248             }
249              
250             {
251             my $max_int_bytes = log( ~0 ) / ( 8 * log(2) );
252              
253             sub _decode_uint {
254 165     165   359 my $self = shift;
255 165         275 my $buffer = shift;
256 165         262 my $size = shift;
257              
258 165         240 if (DEBUG) {
259             $self->_debug_string( 'UINT size', $size );
260             $self->_debug_binary( 'Buffer', $buffer );
261             }
262              
263 165 100       423 my $int = $size <= $max_int_bytes ? 0 : Math::BigInt->bzero;
264 165 100       604 return $int if $size == 0;
265              
266 141         475 my @unpacked = unpack( 'C*', $buffer );
267 141         350 for my $piece (@unpacked) {
268 382         48308 $int = ( $int << 8 ) | $piece;
269             }
270              
271 141         4951 return $int;
272             }
273             }
274              
275             sub _decode_array {
276 24     24   49 my $self = shift;
277 24         56 my $size = shift;
278 24         67 my $offset = shift;
279              
280 24         38 $self->_debug_string( 'Array size', $size )
281             if DEBUG;
282              
283 24         54 my @array;
284 24         126 for ( 1 .. $size ) {
285 43         138 ( my $val, $offset ) = $self->decode($offset);
286              
287 43         2448 if (DEBUG) {
288             $self->_debug_string( "Value $_", $val );
289             }
290              
291 43         144 push @array, $val;
292             }
293              
294 24         56 $self->_debug_structure( 'Decoded array', \@array )
295             if DEBUG;
296              
297 24 100       133 return wantarray ? ( \@array, $offset ) : \@array;
298             }
299              
300             sub _decode_container {
301 1     1   10 return MaxMind::DB::Reader::Data::Container->new;
302             }
303              
304             sub _decode_end_marker {
305 1     1   15 return MaxMind::DB::Reader::Data::EndMarker->new;
306             }
307              
308             sub _decode_boolean {
309 2     2   5 my $self = shift;
310 2         3 my $size = shift;
311 2         4 my $offset = shift;
312              
313 2 50       8 return wantarray ? ( $size, $offset ) : $size;
314             }
315             ## use critic
316              
317             sub _verify_size {
318 35     35   55 my $self = shift;
319 35         57 my $expected = shift;
320 35         63 my $actual = shift;
321              
322 35 100       348 confess
323             q{The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)}
324             unless $expected == $actual;
325             }
326              
327             sub _size_from_ctrl_byte {
328 1200     1200   2049 my $self = shift;
329 1200         1946 my $ctrl_byte = shift;
330 1200         1817 my $offset = shift;
331              
332 1200         1994 my $size = $ctrl_byte & 0b00011111;
333 1200 100       3511 return ( $size, $offset )
334             if $size < 29;
335              
336 14         33 my $bytes_to_read = $size - 28;
337              
338 14         25 my $buffer;
339 14         73 $self->_read( \$buffer, $offset, $bytes_to_read );
340              
341 14 100       68 if ( $size == 29 ) {
    100          
342 8         39 $size = 29 + unpack( 'C', $buffer );
343             }
344             elsif ( $size == 30 ) {
345 4         15 $size = 285 + unpack( 'n', $buffer );
346             }
347             else {
348 2         915 $size = 65821 + unpack( 'N', $self->_zero_pad_left( $buffer, 4 ) );
349             }
350              
351 14         49 return ( $size, $offset + $bytes_to_read );
352             }
353              
354             sub _zero_pad_left {
355 54     54   6307 my $self = shift;
356 54         91 my $content = shift;
357 54         94 my $desired_length = shift;
358              
359 54         243 return ( "\x00" x ( $desired_length - length($content) ) ) . $content;
360             }
361              
362             1;