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   761711 use strict;
  21         35  
  21         553  
4 21     21   81 use warnings;
  21         30  
  21         570  
5 21     21   7076 use namespace::autoclean;
  21         225627  
  21         95  
6 21     21   1147 use autodie;
  21         32  
  21         153  
7              
8             our $VERSION = '1.000012';
9              
10 21     21   70012 use Carp qw( confess );
  21         34  
  21         1353  
11 21     21   10825 use Data::IEEE754 qw( unpack_double_be unpack_float_be );
  21         22682  
  21         1185  
12 21     21   10885 use Encode ();
  21         168218  
  21         508  
13 21     21   3552 use Math::BigInt qw();
  21         73658  
  21         653  
14 21     21   10255 use MaxMind::DB::Common 0.040001 qw( %TypeNumToName );
  21         8363  
  21         3121  
15 21     21   10122 use MaxMind::DB::Reader::Data::Container;
  21         37  
  21         556  
16 21     21   7878 use MaxMind::DB::Reader::Data::EndMarker;
  21         38  
  21         587  
17 21     21   6590 use MaxMind::DB::Types qw( Int );
  21         185124  
  21         1176  
18              
19 21     21   8439 use Moo;
  21         34530  
  21         131  
20 21     21   28450 use MooX::StrictConstructor;
  21         162952  
  21         117  
21              
22             with 'MaxMind::DB::Role::Debugs', 'MaxMind::DB::Reader::Role::Sysreader';
23              
24 21     21   204245 use constant DEBUG => $ENV{MAXMIND_DB_DECODER_DEBUG};
  21         45  
  21         1696  
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   96 use constant POINTER_TEST_HACK => $ENV{MAXMIND_DB_POINTER_TEST_HACK};
  21         29  
  21         31795  
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 403529 my $self = shift;
42 1237         1166 my $offset = shift;
43              
44 1237 100       2274 confess 'You must provide an offset to decode from when calling ->decode'
45             unless defined $offset;
46              
47 1236 100       3057 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         38273 if (DEBUG) {
52             $self->_debug_newline();
53             $self->_debug_string( 'Offset', $offset );
54             }
55              
56 1235         1128 my $ctrl_byte;
57 1235         3091 $self->_read( \$ctrl_byte, $offset, 1 );
58 1235         1301 $offset++;
59              
60 1235         969 $self->_debug_binary( 'Control byte', $ctrl_byte )
61             if DEBUG;
62              
63 1235         2242 $ctrl_byte = unpack( C => $ctrl_byte );
64              
65             # The type is encoded in the first 3 bits of the byte.
66 1235         2610 my $type = $TypeNumToName{ $ctrl_byte >> 5 };
67              
68 1235         875 $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       2128 if ( $type eq 'pointer' ) {
74 37         96 my ( $pointer, $new_offset )
75             = $self->_decode_pointer( $ctrl_byte, $offset );
76              
77 37         59 return $pointer if POINTER_TEST_HACK;
78              
79 27         75 my $value = $self->decode($pointer);
80             return wantarray
81 26 100       938 ? ( $value, $new_offset )
82             : $value;
83             }
84              
85 1198 100       1908 if ( $type eq 'extended' ) {
86 108         112 my $next_byte;
87 108         275 $self->_read( \$next_byte, $offset, 1 );
88              
89 108         87 $self->_debug_binary( 'Next byte', $next_byte )
90             if DEBUG;
91              
92 108         232 my $type_num = unpack( C => $next_byte ) + 7;
93 108 50       216 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         196 $type = $TypeNumToName{$type_num};
98 108         121 $offset++;
99             }
100              
101 1198         2594 ( my $size, $offset )
102             = $self->_size_from_ctrl_byte( $ctrl_byte, $offset );
103              
104 1198         1112 $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       2160 return $self->_decode_map( $size, $offset )
110             if $type eq 'map';
111              
112 977 100       1602 return $self->_decode_array( $size, $offset )
113             if $type eq 'array';
114              
115 953 100       1422 return $self->_decode_boolean( $size, $offset )
116             if $type eq 'boolean';
117              
118 951         857 my $buffer;
119 951 100       2750 $self->_read( \$buffer, $offset, $size )
120             if $size;
121              
122 951         756 $self->_debug_binary( 'Buffer', $buffer )
123             if DEBUG;
124              
125 951 100       2746 my $method = '_decode_' . ( $type =~ /^uint/ ? 'uint' : $type );
126             return wantarray
127 951 100       3014 ? ( $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   40 my $self = shift;
140 37         36 my $ctrl_byte = shift;
141 37         41 my $offset = shift;
142              
143 37         56 my $pointer_size = ( ( $ctrl_byte >> 3 ) & 0b00000011 ) + 1;
144              
145 37         31 $self->_debug_string( 'Pointer size', $pointer_size )
146             if DEBUG;
147              
148 37         27 my $buffer;
149 37         113 $self->_read( \$buffer, $offset, $pointer_size );
150              
151 37         39 $self->_debug_binary( 'Buffer', $buffer )
152             if DEBUG;
153              
154 37 100       230 my $packed
155             = $pointer_size == 4
156             ? $buffer
157             : ( pack( C => $ctrl_byte & 0b00000111 ) ) . $buffer;
158              
159 37         93 $packed = $self->_zero_pad_left( $packed, 4 );
160              
161 37         35 $self->_debug_binary( 'Packed pointer', $packed )
162             if DEBUG;
163              
164 37         140 my $pointer = unpack( 'N' => $packed ) + $self->_pointer_base();
165 37         63 $pointer += $pointer_value_offset{$pointer_size};
166              
167 37         38 $self->_debug_string( 'Pointer to', $pointer )
168             if DEBUG;
169              
170 37         71 return ( $pointer, $offset + $pointer_size );
171             }
172              
173             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
174             sub _decode_utf8_string {
175 727     727   706 my $self = shift;
176 727         647 my $buffer = shift;
177 727         662 my $size = shift;
178              
179 727 100       1124 return q{} if $size == 0;
180              
181             ## no critic (Subroutines::ProhibitCallsToUnexportedSubs)
182 726         2608 return Encode::decode( 'utf-8', $buffer, Encode::FB_CROAK );
183             }
184              
185             sub _decode_double {
186 17     17   21 my $self = shift;
187 17         19 my $buffer = shift;
188 17         16 my $size = shift;
189              
190 17         41 $self->_verify_size( 8, $size );
191 16         41 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         13 my $size = shift;
198              
199 18         26 $self->_verify_size( 4, $size );
200 18         37 return unpack_float_be($buffer);
201             }
202              
203             sub _decode_bytes {
204 11     11   11 my $self = shift;
205 11         8 my $buffer = shift;
206 11         12 my $size = shift;
207              
208 11 100       21 return q{} if $size == 0;
209              
210 10         22 return $buffer;
211             }
212              
213             sub _decode_map {
214 221     221   238 my $self = shift;
215 221         229 my $size = shift;
216 221         232 my $offset = shift;
217              
218 221         153 $self->_debug_string( 'Map size', $size )
219             if DEBUG;
220              
221 221         225 my %map;
222 221         638 for ( 1 .. $size ) {
223 420         1087 ( my $key, $offset ) = $self->decode($offset);
224 420         17718 ( my $val, $offset ) = $self->decode($offset);
225              
226 418         7674 if (DEBUG) {
227             $self->_debug_string( "Key $_", $key );
228             $self->_debug_string( "Value $_", $val );
229             }
230              
231 418         1451 $map{$key} = $val;
232             }
233              
234 219         255 $self->_debug_structure( 'Decoded map', \%map )
235             if DEBUG;
236              
237 219 100       2096 return wantarray ? ( \%map, $offset ) : \%map;
238             }
239              
240             sub _decode_int32 {
241 12     12   15 my $self = shift;
242 12         12 my $buffer = shift;
243 12         12 my $size = shift;
244              
245 12 100       29 return 0 if $size == 0;
246              
247 11         26 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   179 my $self = shift;
255 164         146 my $buffer = shift;
256 164         146 my $size = shift;
257              
258 164         148 if (DEBUG) {
259             $self->_debug_string( 'UINT size', $size );
260             $self->_debug_binary( 'Buffer', $buffer );
261             }
262              
263 164 100       301 my $int = $size <= $max_int_bytes ? 0 : Math::BigInt->bzero();
264 164 100       491 return $int if $size == 0;
265              
266 140         379 my @unpacked = unpack( 'C*', $buffer );
267 140         256 for my $piece (@unpacked) {
268 369         25592 $int = ( $int << 8 ) | $piece;
269             }
270              
271 140         2514 return $int;
272             }
273             }
274              
275             sub _decode_array {
276 24     24   33 my $self = shift;
277 24         32 my $size = shift;
278 24         34 my $offset = shift;
279              
280 24         26 $self->_debug_string( 'Array size', $size )
281             if DEBUG;
282              
283 24         34 my @array;
284 24         75 for ( 1 .. $size ) {
285 43         82 ( my $val, $offset ) = $self->decode($offset);
286              
287 43         1506 if (DEBUG) {
288             $self->_debug_string( "Value $_", $val );
289             }
290              
291 43         133 push @array, $val;
292             }
293              
294 24         30 $self->_debug_structure( 'Decoded array', \@array )
295             if DEBUG;
296              
297 24 100       100 return wantarray ? ( \@array, $offset ) : \@array;
298             }
299              
300             sub _decode_container {
301 1     1   12 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   2 my $self = shift;
310 2         2 my $size = shift;
311 2         2 my $offset = shift;
312              
313 2 50       6 return wantarray ? ( $size, $offset ) : $size;
314             }
315             ## use critic
316              
317             sub _verify_size {
318 35     35   31 my $self = shift;
319 35         36 my $expected = shift;
320 35         23 my $actual = shift;
321              
322 35 100       321 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   1192 my $self = shift;
329 1198         1138 my $ctrl_byte = shift;
330 1198         1100 my $offset = shift;
331              
332 1198         1141 my $size = $ctrl_byte & 0b00011111;
333 1198 100       2800 return ( $size, $offset )
334             if $size < 29;
335              
336 14         25 my $bytes_to_read = $size - 28;
337              
338 14         14 my $buffer;
339 14         34 $self->_read( \$buffer, $offset, $bytes_to_read );
340              
341 14 100       46 if ( $size == 29 ) {
    100          
342 8         20 $size = 29 + unpack( 'C', $buffer );
343             }
344             elsif ( $size == 30 ) {
345 4         11 $size = 285 + unpack( 'n', $buffer );
346             }
347             else {
348 2         11 $size = 65821 + unpack( 'N', $self->_zero_pad_left( $buffer, 4 ) );
349             }
350              
351 14         30 return ( $size, $offset + $bytes_to_read );
352             }
353              
354             sub _zero_pad_left {
355 53     53   5167 my $self = shift;
356 53         59 my $content = shift;
357 53         61 my $desired_length = shift;
358              
359 53         215 return ( "\x00" x ( $desired_length - length($content) ) ) . $content;
360             }
361              
362             1;