File Coverage

blib/lib/MaxMind/DB/Writer/Serializer.pm
Criterion Covered Total %
statement 213 216 98.6
branch 53 58 91.3
condition 11 12 91.6
subroutine 40 41 97.5
pod 0 1 0.0
total 317 328 96.6


line stmt bran cond sub pod time code
1             package MaxMind::DB::Writer::Serializer;
2              
3 37     37   3464908 use strict;
  37         112  
  37         1610  
4 37     37   236 use warnings;
  37         79  
  37         2280  
5 37     37   9476 use namespace::autoclean;
  37         257961  
  37         300  
6              
7             our $VERSION = '0.300004';
8              
9             require bytes;
10 37     37   4798 use Carp qw( confess );
  37         125  
  37         3764  
11 37     37   15648 use Data::IEEE754 qw( pack_double_be pack_float_be );
  37         49841  
  37         3237  
12 37     37   21976 use Encode qw( encode is_utf8 FB_CROAK );
  37         590160  
  37         5066  
13 37     37   10980 use Math::Int128 qw( uint128_to_net );
  37         130784  
  37         3902  
14 37     37   8975 use MaxMind::DB::Common 0.031000 qw( %TypeNameToNum );
  37         9946  
  37         5495  
15 37     37   25105 use MaxMind::DB::Writer::Util qw( key_for_data );
  37         139  
  37         2632  
16              
17 37     37   23679 use Moose;
  37         21846332  
  37         545  
18 37     37   373049 use MooseX::StrictConstructor;
  37         1031542  
  37         199  
19              
20             with 'MaxMind::DB::Role::Debugs';
21              
22             ## no critic (ValuesAndExpressions::ProhibitConstantPragma)
23 37     37   446387 use constant DEBUG => $ENV{MAXMIND_DB_SERIALIZER_DEBUG};
  37         288  
  37         4413  
24 37     37   314 use constant VERIFY => $ENV{MAXMIND_DB_SERIALIZER_VERIFY};
  37         104  
  37         137527  
25              
26             if (VERIFY) {
27             require MaxMind::DB::Reader::Decoder;
28             require Test::Deep::NoTest;
29             Test::Deep::NoTest->import(qw( cmp_details deep_diag ));
30             }
31              
32             if (DEBUG) {
33             binmode STDERR, ':encoding(UTF-8)' or die $!;
34             }
35              
36             has buffer => (
37             is => 'ro',
38             isa => 'ScalarRef[Str]',
39             init_arg => undef,
40             lazy => 1,
41             default => sub {
42             my $buffer = q{};
43             return \$buffer;
44             },
45             );
46              
47             has _map_key_type_callback => (
48             is => 'ro',
49             isa => 'CodeRef',
50             init_arg => 'map_key_type_callback',
51             required => 1,
52             );
53              
54             # This is settable so we can more easily test the encoding portion of the code
55             # without letting the deduplication interfere and turn a data item into a
56             # pointer. In normal use this should always be true.
57             has _deduplicate_data => (
58             is => 'ro',
59             isa => 'Bool',
60             default => 1,
61             );
62              
63             has _cache => (
64             traits => ['Hash'],
65             is => 'ro',
66             isa => 'HashRef',
67             init_arg => undef,
68             lazy => 1,
69             default => sub { {} },
70             handles => {
71             _save_position => 'set',
72             _position_for_data => 'get',
73             },
74             );
75              
76             has _decoder => (
77             is => 'ro',
78             isa => 'MaxMind::DB::Reader::Decoder',
79             init_arg => undef,
80             lazy => 1,
81             builder => '_build_decoder',
82             );
83              
84             my $MinimumCacheableSize = 4;
85              
86             sub store_data {
87 11911     11911 0 681052 my $self = shift;
88 11911         21169 my $type = shift;
89 11911         22443 my $data = shift;
90 11911         18843 my $member_type = shift;
91 11911         19367 my $key_for_data = shift;
92              
93 11911 50       25018 confess 'Cannot store an undef as data'
94             unless defined $data;
95              
96 11911         17747 $self->_debug_newline()
97             if DEBUG;
98 11911         15336 $self->_debug_string( 'Storing type', $type )
99             if DEBUG;
100              
101 11911 100       29521 return $self->_store_data( $type, $data, $member_type )
102             unless $self->_should_cache_value( $type, $data );
103              
104 6745   66     51091 $key_for_data //= key_for_data($data);
105              
106 6745         11339 $self->_debug_string( 'Cache key', $key_for_data )
107             if DEBUG;
108              
109 6745         347342 my $position = $self->_position_for_data($key_for_data);
110              
111 6745 100       13868 if ( defined $position ) {
112 1289         1787 if (DEBUG) {
113             $self->_debug_string( 'Found data at position', $position );
114             $self->_debug_string( 'Storing pointer to', $position );
115             }
116              
117 1289         3592 return $self->_store_data( pointer => $position );
118             }
119             else {
120 5456         16768 my $stored_position
121             = $self->_store_data( $type, $data, $member_type );
122 5456         8323 $self->_debug_string( 'Stored data at position', $stored_position )
123             if DEBUG;
124 5456         269704 $self->_save_position( $key_for_data => $stored_position );
125              
126 5456         192892 return $stored_position;
127             }
128             }
129              
130             if (VERIFY) {
131             around store_data => sub {
132             my $orig = shift;
133             my $self = shift;
134             my $type = shift;
135             my $data = shift;
136             my $member_type = shift;
137              
138             my $position = $self->$orig( $type, $data, $member_type );
139              
140             my $stored_data = $self->_decoder()->decode($position);
141             my ( $ok, $stack ) = cmp_details( $data, $stored_data );
142              
143             unless ($ok) {
144             my $diag = deep_diag($stack);
145             die
146             "Data we just stored does not decode to value we expected:\n$diag\n";
147             }
148              
149             return $position;
150             };
151             }
152              
153             # These types never take more than 4 bytes to store.
154             my %NeverCache = map { $_ => 1 } qw(
155             int32
156             uint16
157             uint32
158             );
159              
160             sub _should_cache_value {
161 11911     11911   18080 my $self = shift;
162 11911         19757 my $type = shift;
163 11911         19318 my $data = shift;
164              
165 11911 100       475831 return 0 unless $self->_deduplicate_data();
166              
167 11780 100       33784 if ( $NeverCache{$type} ) {
168 2796         3759 $self->_debug_string( 'Never cache type', $type )
169             if DEBUG;
170 2796         10425 return 0;
171             }
172              
173 8984 100 100     42432 if ( $type eq 'uint64' || $type eq 'uint128' ) {
    100          
174 241         2225 ( my $non_zero = $data ) =~ s/^0+//;
175              
176 241         1460 my $stored_bytes = ( length($non_zero) / 4 );
177              
178 241         716 $self->_debug_string( "Space needed for $type $data", $stored_bytes )
179             if DEBUG;
180              
181             # We can store four hex digits per byte. Once we strip leading zeros,
182             # we know how much space this number will take to store.
183 241         992 return $stored_bytes >= $MinimumCacheableSize;
184             }
185             elsif ( ref $data ) {
186 2205         2997 $self->_debug_message('Always cache references')
187             if DEBUG;
188 2205         5641 return 1;
189             }
190             else {
191             ## no critic (ProhibitCallsToUnexportedSubs)
192 6538         9347 $self->_debug_string(
193             "Space needed for $type $data",
194             bytes::length $data
195             ) if DEBUG;
196              
197 6538         18460 return bytes::length($data) >= $MinimumCacheableSize;
198             }
199             }
200              
201             sub _store_data {
202 11911     11911   30268 my $self = shift;
203 11911         18144 my $type = shift;
204 11911         17018 my $data = shift;
205 11911         16249 my $member_type = shift;
206              
207             ## no critic (ProhibitCallsToUnexportedSubs)
208 11911         17743 my $current_position = bytes::length ${ $self->buffer() };
  11911         427447  
209              
210 11911         48544 my $method = '_encode_' . $type;
211 11911         42988 $self->$method( $data, $member_type );
212              
213             # We don't add 1 byte because the first byte we can point to is byte 0
214             # (not 1).
215 11910         53389 return $current_position;
216             }
217              
218             my @pointer_thresholds = (
219             {
220             cutoff => 2**11,
221             offset => 0,
222             }
223             );
224             push @pointer_thresholds,
225             {
226             cutoff => 2**19 + $pointer_thresholds[-1]{cutoff},
227             offset => $pointer_thresholds[-1]{cutoff},
228             };
229             push @pointer_thresholds,
230             {
231             cutoff => 2**27 + $pointer_thresholds[-1]{cutoff},
232             offset => $pointer_thresholds[-1]{cutoff},
233             };
234             push @pointer_thresholds,
235             {
236             cutoff => 2**32,
237             offset => 0,
238             };
239              
240             ## no critic (ProhibitUnusedPrivateSubroutines)
241             sub _encode_pointer {
242 1303     1303   7248 my $self = shift;
243 1303         2086 my $value = shift;
244              
245 1303         3478 $self->_require_x_bits_unsigned_integer( 32, $value );
246              
247             my $ctrl_byte
248 1299         3746 = ord( $self->_control_bytes( $TypeNameToNum{pointer}, 0 ) );
249              
250 1299         2220 my @value_bytes;
251 1299         2970 for my $n ( 0 .. 3 ) {
252 1312 100       3617 if ( $value < $pointer_thresholds[$n]{cutoff} ) {
253              
254 1299         2722 my $pack_method = '_pack_' . ( $n + 1 ) . '_byte_pointer';
255             @value_bytes = split //,
256             $self->$pack_method(
257 1299         4606 $value - $pointer_thresholds[$n]{offset} );
258              
259 1299 100       2915 if ( $n == 3 ) {
260 2         4 $ctrl_byte |= ( 3 << 3 );
261             }
262             else {
263 1297         2622 $ctrl_byte |= ( $n << 3 ) | ord( shift @value_bytes );
264             }
265              
266 1299         2677 last;
267             }
268             }
269              
270 1299         3823 $self->_write_encoded_data( pack( 'C', $ctrl_byte ), @value_bytes );
271             }
272              
273             sub _pack_1_byte_pointer {
274 1292     1292   4529 return pack( n => $_[1] );
275             }
276              
277             sub _pack_2_byte_pointer {
278 3     3   19 return substr( pack( N => $_[1] ), 1, 3 );
279             }
280              
281             sub _pack_3_byte_pointer {
282 2     2   11 return pack( N => $_[1] );
283             }
284              
285             sub _pack_4_byte_pointer {
286 2     2   11 return pack( N => $_[1] );
287             }
288              
289             sub _encode_utf8_string {
290 4897     4897   7986 my $self = shift;
291              
292 4897         8760 my $string = shift;
293              
294 4897         28155 $self->_simple_encode(
295             utf8_string => encode( 'UTF-8', $string, FB_CROAK ) );
296             }
297              
298             sub _encode_double {
299 104     104   228 my $self = shift;
300              
301             $self->_write_encoded_data(
302 104         431 $self->_control_bytes( $TypeNameToNum{double}, 8, ),
303             pack_double_be(shift)
304             );
305             }
306              
307             sub _encode_float {
308 105     105   222 my $self = shift;
309              
310             $self->_write_encoded_data(
311 105         895 $self->_control_bytes( $TypeNameToNum{float}, 4, ),
312             pack_float_be(shift)
313             );
314             }
315              
316             sub _encode_bytes {
317 107     107   246 my $self = shift;
318              
319 107         233 my $bytes = shift;
320 107 100       548 die "You attempted to store a characters string ($bytes) as bytes"
321             if is_utf8($bytes);
322              
323 106         444 $self->_simple_encode( bytes => $bytes );
324             }
325              
326             sub _encode_uint16 {
327 254     254   543 my $self = shift;
328              
329 254         843 $self->_encode_unsigned_int( 16 => @_ );
330             }
331              
332             sub _encode_uint32 {
333 2462     2462   6112 my $self = shift;
334              
335 2462         6445 $self->_encode_unsigned_int( 32 => @_ );
336             }
337              
338             sub _encode_map {
339 1977     1977   3059 my $self = shift;
340 1977         2822 my $map = shift;
341              
342             $self->_write_encoded_data(
343 1977         4073 $self->_control_bytes( $TypeNameToNum{map}, scalar keys %{$map} ) );
  1977         7323  
344              
345             # We sort to make testing possible.
346 1977         4397 for my $k ( sort keys %{$map} ) {
  1977         7605  
347 3450         10541 $self->store_data( utf8_string => $k );
348              
349 3450         11342 my $value_type = $self->_type_for_key( $k, $map->{$k} );
350 3450         5672 my $array_value_type;
351 3450 100       6764 if ( ref $value_type ) {
352 232         456 ( $value_type, $array_value_type ) = @{$value_type};
  232         616  
353             }
354              
355 3450         8944 $self->store_data( $value_type, $map->{$k}, $array_value_type );
356             }
357             }
358              
359             sub _encode_array {
360 235     235   482 my $self = shift;
361 235         526 my $array = shift;
362 235         431 my $value_type = shift;
363              
364 235 50       836 die 'No value type for array!' unless defined $value_type;
365              
366             $self->_write_encoded_data(
367 235         688 $self->_control_bytes( $TypeNameToNum{array}, scalar @{$array} ) );
  235         818  
368              
369 235         498 $self->store_data( $value_type, $_ ) for @{$array};
  235         1160  
370             }
371              
372             sub _type_for_key {
373 3451     3451   5273 my $self = shift;
374 3451         6116 my $key = shift;
375 3451         5159 my $value = shift;
376              
377 3451         134734 my $type = $self->_map_key_type_callback->( $key, $value );
378              
379 3451 100       19236 die qq{Could not determine the type for map key "$key"}
380             unless $type;
381              
382 3450         6897 return $type;
383             }
384              
385             sub _encode_int32 {
386 108     108   242 my $self = shift;
387 108         193 my $value = shift;
388              
389 108         385 my $encoded_value = pack( 'N!' => $value );
390 108         295 $encoded_value =~ s/^\x00+//;
391              
392             $self->_write_encoded_data(
393             $self->_control_bytes(
394 108         388 $TypeNameToNum{int32}, length($encoded_value)
395             ),
396             $encoded_value,
397             );
398             }
399              
400             sub _encode_uint64 {
401 153     153   5074 my $self = shift;
402              
403 153         571 $self->_encode_unsigned_int( 64 => @_ );
404             }
405              
406             sub _encode_uint128 {
407 121     121   237 my $self = shift;
408              
409 121         378 $self->_encode_unsigned_int( 128 => @_ );
410             }
411              
412             sub _encode_boolean {
413 98     98   233 my $self = shift;
414 98         208 my $value = shift;
415              
416             $self->_write_encoded_data(
417 98 100       2646 $self->_control_bytes( $TypeNameToNum{boolean}, $value ? 1 : 0 ) );
418             }
419              
420             sub _encode_end_marker {
421 1     1   33 my $self = shift;
422              
423 1         5 $self->_simple_encode( 'end_marker', q{} );
424             }
425              
426             sub _simple_encode {
427 5004     5004   512138 my $self = shift;
428 5004         8482 my $type = shift;
429 5004         66993 my $value = shift;
430              
431             $self->_write_encoded_data(
432 5004         18085 $self->_control_bytes( $TypeNameToNum{$type}, length($value) ),
433             $value,
434             );
435             }
436              
437             sub _encode_unsigned_int {
438 2990     2990   4156 my $self = shift;
439 2990         4747 my $bits = shift;
440 2990         4306 my $value = shift;
441              
442 2990         7850 $self->_require_x_bits_unsigned_integer( $bits, $value );
443              
444 2982         6399 my $encoded_value;
445 2982 100       5657 if ( $bits >= 64 ) {
446 270         1168 $encoded_value = uint128_to_net($value);
447             }
448             else {
449 2712         7099 $encoded_value = pack( N => $value );
450             }
451              
452 2982         13790 $encoded_value =~ s/^\x00+//;
453              
454             $self->_write_encoded_data(
455             $self->_control_bytes(
456 2982         11051 $TypeNameToNum{ 'uint' . $bits },
457             length($encoded_value)
458             ),
459             $encoded_value,
460             );
461             }
462             ## use critic
463              
464             {
465             my %Max = (
466             16 => ( 2**16 ) - 1,
467             32 => ( 2**32 ) - 1,
468             );
469              
470             sub _require_x_bits_unsigned_integer {
471 4293     4293   5729 my $self = shift;
472 4293         8326 my $bits = shift;
473 4293         5935 my $value = shift;
474              
475 4293         8686 my $type_description = "unsigned $bits-bit integer";
476              
477 4293 100       9374 die "You cannot encode undef as an $type_description."
478             unless defined $value;
479              
480 4290 100       8550 if ( $bits >= 64 ) {
481 273 100 100     2477 if ( blessed $value && $value->isa('Math::UInt128') ) {
482 231 100 100     2433 die
483             "You cannot encode $value as an $type_description. It is too big."
484             if $bits != 128 && $value / ( 2**$bits ) > 1;
485             }
486             else {
487 42 100       196 die
488             "You cannot encode $value as an $type_description. It is not an unsigned integer number."
489             unless $value =~ /^[0-9]+$/;
490             }
491             }
492             else {
493 4017 100       14197 die
494             "You cannot encode $value as an $type_description. It is not an unsigned integer number."
495             unless $value =~ /^[0-9]+$/;
496              
497             die
498             "You cannot encode $value as an $type_description. It is too big."
499 4013 100       12715 if $value > $Max{$bits};
500              
501             }
502             }
503             }
504              
505             {
506             # The value is the threshold for needing another byte to store the size
507             # value. In other words, a size of 28 fits in one byte, a size of 29 needs
508             # two bytes.
509             my %ThresholdSize = (
510             1 => 29,
511             2 => 29 + 256,
512             3 => 29 + 256 + 2**16,
513             4 => 29 + 256 + 2**16 + 2**24,
514             );
515              
516             sub _control_bytes {
517 11912     11912   19416 my $self = shift;
518 11912         19493 my $type = shift;
519 11912         17933 my $size = shift;
520              
521 11912 100       31311 if ( $size >= $ThresholdSize{4} ) {
522             die "Cannot store $size bytes - max size is "
523 1         3131 . ( $ThresholdSize{4} - 1 )
524             . ' bytes';
525             }
526              
527 11911         18107 my $template = 'C';
528              
529 11911         18457 my $first_byte;
530             my $second_byte;
531 11911 100       27387 if ( $type < 8 ) {
532 11094         18015 $first_byte = ( $type << 5 );
533             }
534             else {
535 817         1688 $first_byte = ( $TypeNameToNum{extended} << 5 );
536 817         1251 $second_byte = $type - 7;
537 817         1506 $template .= 'C';
538             }
539              
540 11911         16718 my $leftover_size;
541             ## no critic (ControlStructures::ProhibitCascadingIfElse)
542 11911 100       26296 if ( $size < $ThresholdSize{1} ) {
    100          
    100          
    50          
543 9844         15255 $first_byte |= $size;
544             }
545             elsif ( $size <= $ThresholdSize{2} ) {
546 4         10 $first_byte |= 29;
547 4         10 $leftover_size = $size - $ThresholdSize{1};
548 4         9 $template .= 'C';
549             }
550             elsif ( $size <= $ThresholdSize{3} ) {
551 2060         3862 $first_byte |= 30;
552 2060         3528 $leftover_size = $size - $ThresholdSize{2};
553 2060         3943 $template .= 'n';
554             }
555             elsif ( $size <= $ThresholdSize{4} ) {
556 3         8 $first_byte |= 31;
557              
558             # There's no nice way to express "pack an integer into 24 bits"
559             # using a pack template, so we'll just pack it here and then chop
560             # off the first byte.
561             $leftover_size
562 3         26 = substr( pack( N => $size - $ThresholdSize{3} ), 1 );
563 3         9 $template .= 'a3';
564             }
565              
566             return pack(
567 11911         24698 $template => grep { defined } (
  35733         98700  
568             $first_byte,
569             $second_byte,
570             $leftover_size,
571             )
572             );
573             }
574             }
575              
576             sub _write_encoded_data {
577 11911     11911   19011 my $self = shift;
578              
579 11911         24336 ${ $self->buffer() } .= $_ for @_;
  21525         794782  
580              
581 11911         17909 $self->_debug_binary( 'Wrote', join q{}, @_ )
582             if DEBUG;
583              
584 11911         27664 return;
585             }
586              
587             sub _build_decoder {
588 0     0     my $self = shift;
589              
590             ## no critic (InputOutput::RequireBriefOpen)
591 0 0         open my $fh, '<:raw', $self->buffer() or die $!;
592              
593 0           return MaxMind::DB::Reader::Decoder->new(
594             data_source => $fh,
595             );
596             }
597              
598             __PACKAGE__->meta()->make_immutable();
599              
600             1;