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   601026 use strict;
  21         31  
  21         462  
4 21     21   60 use warnings;
  21         21  
  21         387  
5 21     21   5794 use namespace::autoclean;
  21         134661  
  21         71  
6 21     21   909 use autodie;
  21         24  
  21         114  
7              
8             our $VERSION = '1.000013';
9              
10 21     21   61729 use Carp qw( confess );
  21         31  
  21         1042  
11 21     21   9047 use Data::IEEE754 qw( unpack_double_be unpack_float_be );
  21         20693  
  21         1012  
12 21     21   9731 use Encode ();
  21         146935  
  21         394  
13 21     21   3497 use Math::BigInt qw();
  21         62622  
  21         464  
14 21     21   8004 use MaxMind::DB::Common 0.040001 qw( %TypeNumToName );
  21         6485  
  21         2402  
15 21     21   8616 use MaxMind::DB::Reader::Data::Container;
  21         39  
  21         437  
16 21     21   7501 use MaxMind::DB::Reader::Data::EndMarker;
  21         36  
  21         495  
17 21     21   5377 use MaxMind::DB::Types qw( Int );
  21         145287  
  21         941  
18              
19 21     21   6647 use Moo;
  21         29448  
  21         100  
20 21     21   23343 use MooX::StrictConstructor;
  21         136026  
  21         92  
21              
22             with 'MaxMind::DB::Role::Debugs', 'MaxMind::DB::Reader::Role::Sysreader';
23              
24 21     21   170686 use constant DEBUG => $ENV{MAXMIND_DB_DECODER_DEBUG};
  21         44  
  21         1254  
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   78 use constant POINTER_TEST_HACK => $ENV{MAXMIND_DB_POINTER_TEST_HACK};
  21         23  
  21         26387  
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 1237     1237 0 346086 my $self = shift;
42 1237         1096 my $offset = shift;
43              
44 1237 100       2024 confess 'You must provide an offset to decode from when calling ->decode'
45             unless defined $offset;
46              
47 1236 100       2524 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 1235         31373 if (DEBUG) {
52             $self->_debug_newline();
53             $self->_debug_string( 'Offset', $offset );
54             }
55              
56 1235         899 my $ctrl_byte;
57 1235         2711 $self->_read( \$ctrl_byte, $offset, 1 );
58 1235         1017 $offset++;
59              
60 1235         837 $self->_debug_binary( 'Control byte', $ctrl_byte )
61             if DEBUG;
62              
63 1235         1811 $ctrl_byte = unpack( C => $ctrl_byte );
64              
65             # The type is encoded in the first 3 bits of the byte.
66 1235         1839 my $type = $TypeNumToName{ $ctrl_byte >> 5 };
67              
68 1235         846 $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 1235 100       2095 if ( $type eq 'pointer' ) {
74 37         74 my ( $pointer, $new_offset )
75             = $self->_decode_pointer( $ctrl_byte, $offset );
76              
77 37         38 return $pointer if POINTER_TEST_HACK;
78              
79 27         57 my $value = $self->decode($pointer);
80             return wantarray
81 26 100       685 ? ( $value, $new_offset )
82             : $value;
83             }
84              
85 1198 100       1469 if ( $type eq 'extended' ) {
86 108         98 my $next_byte;
87 108         247 $self->_read( \$next_byte, $offset, 1 );
88              
89 108         81 $self->_debug_binary( 'Next byte', $next_byte )
90             if DEBUG;
91              
92 108         234 my $type_num = unpack( C => $next_byte ) + 7;
93 108 50       212 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         155 $type = $TypeNumToName{$type_num};
98 108         105 $offset++;
99             }
100              
101 1198         1840 ( my $size, $offset )
102             = $self->_size_from_ctrl_byte( $ctrl_byte, $offset );
103              
104 1198         874 $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 1198 100       1796 return $self->_decode_map( $size, $offset )
110             if $type eq 'map';
111              
112 977 100       1309 return $self->_decode_array( $size, $offset )
113             if $type eq 'array';
114              
115 953 100       1133 return $self->_decode_boolean( $size, $offset )
116             if $type eq 'boolean';
117              
118 951         705 my $buffer;
119 951 100       2461 $self->_read( \$buffer, $offset, $size )
120             if $size;
121              
122 951         670 $self->_debug_binary( 'Buffer', $buffer )
123             if DEBUG;
124              
125 951 100       2021 my $method = '_decode_' . ( $type =~ /^uint/ ? 'uint' : $type );
126             return wantarray
127 951 100       2481 ? ( $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 37     37   36 my $self = shift;
140 37         29 my $ctrl_byte = shift;
141 37         29 my $offset = shift;
142              
143 37         49 my $pointer_size = ( ( $ctrl_byte >> 3 ) & 0b00000011 ) + 1;
144              
145 37         22 $self->_debug_string( 'Pointer size', $pointer_size )
146             if DEBUG;
147              
148 37         27 my $buffer;
149 37         70 $self->_read( \$buffer, $offset, $pointer_size );
150              
151 37         29 $self->_debug_binary( 'Buffer', $buffer )
152             if DEBUG;
153              
154 37 100       114 my $packed
155             = $pointer_size == 4
156             ? $buffer
157             : ( pack( C => $ctrl_byte & 0b00000111 ) ) . $buffer;
158              
159 37         71 $packed = $self->_zero_pad_left( $packed, 4 );
160              
161 37         29 $self->_debug_binary( 'Packed pointer', $packed )
162             if DEBUG;
163              
164 37         100 my $pointer = unpack( 'N' => $packed ) + $self->_pointer_base();
165 37         53 $pointer += $pointer_value_offset{$pointer_size};
166              
167 37         22 $self->_debug_string( 'Pointer to', $pointer )
168             if DEBUG;
169              
170 37         58 return ( $pointer, $offset + $pointer_size );
171             }
172              
173             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
174             sub _decode_utf8_string {
175 727     727   559 my $self = shift;
176 727         547 my $buffer = shift;
177 727         492 my $size = shift;
178              
179 727 100       895 return q{} if $size == 0;
180              
181             ## no critic (Subroutines::ProhibitCallsToUnexportedSubs)
182 726         1915 return Encode::decode( 'utf-8', $buffer, Encode::FB_CROAK );
183             }
184              
185             sub _decode_double {
186 17     17   13 my $self = shift;
187 17         17 my $buffer = shift;
188 17         9 my $size = shift;
189              
190 17         29 $self->_verify_size( 8, $size );
191 16         26 return unpack_double_be($buffer);
192             }
193              
194             sub _decode_float {
195 18     18   13 my $self = shift;
196 18         16 my $buffer = shift;
197 18         10 my $size = shift;
198              
199 18         24 $self->_verify_size( 4, $size );
200 18         28 return unpack_float_be($buffer);
201             }
202              
203             sub _decode_bytes {
204 11     11   8 my $self = shift;
205 11         11 my $buffer = shift;
206 11         9 my $size = shift;
207              
208 11 100       21 return q{} if $size == 0;
209              
210 10         25 return $buffer;
211             }
212              
213             sub _decode_map {
214 221     221   172 my $self = shift;
215 221         194 my $size = shift;
216 221         192 my $offset = shift;
217              
218 221         150 $self->_debug_string( 'Map size', $size )
219             if DEBUG;
220              
221 221         164 my %map;
222 221         420 for ( 1 .. $size ) {
223 420         743 ( my $key, $offset ) = $self->decode($offset);
224 420         13414 ( my $val, $offset ) = $self->decode($offset);
225              
226 418         6638 if (DEBUG) {
227             $self->_debug_string( "Key $_", $key );
228             $self->_debug_string( "Value $_", $val );
229             }
230              
231 418         1129 $map{$key} = $val;
232             }
233              
234 219         157 $self->_debug_structure( 'Decoded map', \%map )
235             if DEBUG;
236              
237 219 100       1285 return wantarray ? ( \%map, $offset ) : \%map;
238             }
239              
240             sub _decode_int32 {
241 12     12   12 my $self = shift;
242 12         10 my $buffer = shift;
243 12         5 my $size = shift;
244              
245 12 100       23 return 0 if $size == 0;
246              
247 11         17 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 164     164   153 my $self = shift;
255 164         146 my $buffer = shift;
256 164         117 my $size = shift;
257              
258 164         121 if (DEBUG) {
259             $self->_debug_string( 'UINT size', $size );
260             $self->_debug_binary( 'Buffer', $buffer );
261             }
262              
263 164 100       227 my $int = $size <= $max_int_bytes ? 0 : Math::BigInt->bzero();
264 164 100       379 return $int if $size == 0;
265              
266 140         283 my @unpacked = unpack( 'C*', $buffer );
267 140         232 for my $piece (@unpacked) {
268 369         22487 $int = ( $int << 8 ) | $piece;
269             }
270              
271 140         2280 return $int;
272             }
273             }
274              
275             sub _decode_array {
276 24     24   32 my $self = shift;
277 24         28 my $size = shift;
278 24         31 my $offset = shift;
279              
280 24         24 $self->_debug_string( 'Array size', $size )
281             if DEBUG;
282              
283 24         28 my @array;
284 24         61 for ( 1 .. $size ) {
285 43         81 ( my $val, $offset ) = $self->decode($offset);
286              
287 43         1289 if (DEBUG) {
288             $self->_debug_string( "Value $_", $val );
289             }
290              
291 43         99 push @array, $val;
292             }
293              
294 24         24 $self->_debug_structure( 'Decoded array', \@array )
295             if DEBUG;
296              
297 24 100       78 return wantarray ? ( \@array, $offset ) : \@array;
298             }
299              
300             sub _decode_container {
301 1     1   9 return MaxMind::DB::Reader::Data::Container->new();
302             }
303              
304             sub _decode_end_marker {
305 1     1   9 return MaxMind::DB::Reader::Data::EndMarker->new();
306             }
307              
308             sub _decode_boolean {
309 2     2   3 my $self = shift;
310 2         1 my $size = shift;
311 2         2 my $offset = shift;
312              
313 2 50       7 return wantarray ? ( $size, $offset ) : $size;
314             }
315             ## use critic
316              
317             sub _verify_size {
318 35     35   20 my $self = shift;
319 35         24 my $expected = shift;
320 35         23 my $actual = shift;
321              
322 35 100       266 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 1198     1198   902 my $self = shift;
329 1198         908 my $ctrl_byte = shift;
330 1198         814 my $offset = shift;
331              
332 1198         1036 my $size = $ctrl_byte & 0b00011111;
333 1198 100       2361 return ( $size, $offset )
334             if $size < 29;
335              
336 14         21 my $bytes_to_read = $size - 28;
337              
338 14         13 my $buffer;
339 14         34 $self->_read( \$buffer, $offset, $bytes_to_read );
340              
341 14 100       37 if ( $size == 29 ) {
    100          
342 8         18 $size = 29 + unpack( 'C', $buffer );
343             }
344             elsif ( $size == 30 ) {
345 4         12 $size = 285 + unpack( 'n', $buffer );
346             }
347             else {
348 2         9 $size = 65821 + unpack( 'N', $self->_zero_pad_left( $buffer, 4 ) );
349             }
350              
351 14         27 return ( $size, $offset + $bytes_to_read );
352             }
353              
354             sub _zero_pad_left {
355 53     53   4717 my $self = shift;
356 53         48 my $content = shift;
357 53         54 my $desired_length = shift;
358              
359 53         164 return ( "\x00" x ( $desired_length - length($content) ) ) . $content;
360             }
361              
362             1;