maxmind-db 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +4 -0
  3. data/LICENSE-APACHE +202 -0
  4. data/LICENSE-MIT +17 -0
  5. data/README.dev.md +30 -0
  6. data/README.md +54 -0
  7. data/Rakefile +12 -0
  8. data/bin/mmdb-benchmark.rb +61 -0
  9. data/lib/maxmind/db.rb +274 -0
  10. data/lib/maxmind/db/decoder.rb +232 -0
  11. data/lib/maxmind/db/errors.rb +8 -0
  12. data/lib/maxmind/db/file_reader.rb +37 -0
  13. data/lib/maxmind/db/memory_reader.rb +24 -0
  14. data/lib/maxmind/db/metadata.rb +61 -0
  15. data/maxmind-db.gemspec +19 -0
  16. data/test/data/LICENSE +4 -0
  17. data/test/data/MaxMind-DB-spec.md +558 -0
  18. data/test/data/MaxMind-DB-test-metadata-pointers.mmdb +0 -0
  19. data/test/data/README.md +4 -0
  20. data/test/data/bad-data/README.md +7 -0
  21. data/test/data/bad-data/libmaxminddb/libmaxminddb-offset-integer-overflow.mmdb +0 -0
  22. data/test/data/bad-data/maxminddb-golang/cyclic-data-structure.mmdb +0 -0
  23. data/test/data/bad-data/maxminddb-golang/invalid-bytes-length.mmdb +1 -0
  24. data/test/data/bad-data/maxminddb-golang/invalid-data-record-offset.mmdb +0 -0
  25. data/test/data/bad-data/maxminddb-golang/invalid-map-key-length.mmdb +0 -0
  26. data/test/data/bad-data/maxminddb-golang/invalid-string-length.mmdb +1 -0
  27. data/test/data/bad-data/maxminddb-golang/metadata-is-an-uint128.mmdb +1 -0
  28. data/test/data/bad-data/maxminddb-golang/unexpected-bytes.mmdb +0 -0
  29. data/test/data/perltidyrc +12 -0
  30. data/test/data/source-data/GeoIP2-Anonymous-IP-Test.json +41 -0
  31. data/test/data/source-data/GeoIP2-City-Test.json +12852 -0
  32. data/test/data/source-data/GeoIP2-Connection-Type-Test.json +102 -0
  33. data/test/data/source-data/GeoIP2-Country-Test.json +11347 -0
  34. data/test/data/source-data/GeoIP2-DensityIncome-Test.json +14 -0
  35. data/test/data/source-data/GeoIP2-Domain-Test.json +452 -0
  36. data/test/data/source-data/GeoIP2-Enterprise-Test.json +673 -0
  37. data/test/data/source-data/GeoIP2-ISP-Test.json +12585 -0
  38. data/test/data/source-data/GeoIP2-Precision-Enterprise-Test.json +1598 -0
  39. data/test/data/source-data/GeoIP2-User-Count-Test.json +2824 -0
  40. data/test/data/source-data/GeoLite2-ASN-Test.json +37 -0
  41. data/test/data/source-data/README +15 -0
  42. data/test/data/test-data/GeoIP2-Anonymous-IP-Test.mmdb +0 -0
  43. data/test/data/test-data/GeoIP2-City-Test-Broken-Double-Format.mmdb +0 -0
  44. data/test/data/test-data/GeoIP2-City-Test-Invalid-Node-Count.mmdb +0 -0
  45. data/test/data/test-data/GeoIP2-City-Test.mmdb +0 -0
  46. data/test/data/test-data/GeoIP2-Connection-Type-Test.mmdb +0 -0
  47. data/test/data/test-data/GeoIP2-Country-Test.mmdb +0 -0
  48. data/test/data/test-data/GeoIP2-DensityIncome-Test.mmdb +0 -0
  49. data/test/data/test-data/GeoIP2-Domain-Test.mmdb +0 -0
  50. data/test/data/test-data/GeoIP2-Enterprise-Test.mmdb +0 -0
  51. data/test/data/test-data/GeoIP2-ISP-Test.mmdb +0 -0
  52. data/test/data/test-data/GeoIP2-Precision-Enterprise-Test.mmdb +0 -0
  53. data/test/data/test-data/GeoIP2-User-Count-Test.mmdb +0 -0
  54. data/test/data/test-data/GeoLite2-ASN-Test.mmdb +0 -0
  55. data/test/data/test-data/MaxMind-DB-no-ipv4-search-tree.mmdb +0 -0
  56. data/test/data/test-data/MaxMind-DB-string-value-entries.mmdb +0 -0
  57. data/test/data/test-data/MaxMind-DB-test-broken-pointers-24.mmdb +0 -0
  58. data/test/data/test-data/MaxMind-DB-test-broken-search-tree-24.mmdb +0 -0
  59. data/test/data/test-data/MaxMind-DB-test-decoder.mmdb +0 -0
  60. data/test/data/test-data/MaxMind-DB-test-ipv4-24.mmdb +0 -0
  61. data/test/data/test-data/MaxMind-DB-test-ipv4-28.mmdb +0 -0
  62. data/test/data/test-data/MaxMind-DB-test-ipv4-32.mmdb +0 -0
  63. data/test/data/test-data/MaxMind-DB-test-ipv6-24.mmdb +0 -0
  64. data/test/data/test-data/MaxMind-DB-test-ipv6-28.mmdb +0 -0
  65. data/test/data/test-data/MaxMind-DB-test-ipv6-32.mmdb +0 -0
  66. data/test/data/test-data/MaxMind-DB-test-metadata-pointers.mmdb +0 -0
  67. data/test/data/test-data/MaxMind-DB-test-mixed-24.mmdb +0 -0
  68. data/test/data/test-data/MaxMind-DB-test-mixed-28.mmdb +0 -0
  69. data/test/data/test-data/MaxMind-DB-test-mixed-32.mmdb +0 -0
  70. data/test/data/test-data/MaxMind-DB-test-nested.mmdb +0 -0
  71. data/test/data/test-data/README.md +26 -0
  72. data/test/data/test-data/maps-with-pointers.raw +0 -0
  73. data/test/data/test-data/write-test-data.pl +620 -0
  74. data/test/data/tidyall.ini +5 -0
  75. data/test/mmdb_util.rb +24 -0
  76. data/test/test_decoder.rb +241 -0
  77. data/test/test_reader.rb +415 -0
  78. metadata +126 -0
@@ -0,0 +1,37 @@
1
+ [
2
+ {
3
+ "::1.128.0.0/107" : {
4
+ "autonomous_system_number" : 1221,
5
+ "autonomous_system_organization" : "Telstra Pty Ltd"
6
+ }
7
+ },
8
+ {
9
+ "::12.81.92.0/118" : {
10
+ "autonomous_system_number" : 7018,
11
+ "autonomous_system_organization" : "AT&T Services"
12
+ }
13
+ },
14
+ {
15
+ "::12.81.96.0/115" : {
16
+ "autonomous_system_number" : 7018
17
+ }
18
+ },
19
+ {
20
+ "2600:6000::/20" : {
21
+ "autonomous_system_number" : 237,
22
+ "autonomous_system_organization" : "Merit Network Inc."
23
+ }
24
+ },
25
+ {
26
+ "2600:7000::/24" : {
27
+ "autonomous_system_number" : 6939,
28
+ "autonomous_system_organization" : "Hurricane Electric, Inc."
29
+ }
30
+ },
31
+ {
32
+ "2600:7100::/24" : {
33
+ "autonomous_system_number" : 237,
34
+ "autonomous_system_organization" : "Merit Network Inc."
35
+ }
36
+ }
37
+ ]
@@ -0,0 +1,15 @@
1
+ All of these but the City and Enterprise files are JSON dumps of the databases
2
+ created by the GeoIP2 build integration tests
3
+ (TestFor::MM::Integration::GeoIP2::Build).
4
+
5
+ Additional data was added to match our legacy test databases.
6
+
7
+ The City file is of unknown origin.
8
+
9
+ The Enterprise file was a single example IP address, modified slightly to
10
+ include all fields. It now has more than that.
11
+
12
+ The Precision file was the normal Enterprise file with an additional marker
13
+ (128.101.101.101) to differentiate the two. It now contains additional
14
+ additions and changes as it is the database used in many MaxMind
15
+ integration tests.
@@ -0,0 +1,26 @@
1
+ The write-test-dbs script will create a small set of test databases with a
2
+ variety of data and record sizes (24, 28, & 32 bit).
3
+
4
+ These test databases are useful for testing code that reads MaxMind DB files.
5
+
6
+ There is also a `maps-with-pointers.raw` file. This contains the raw output of
7
+ the MaxMind::DB::Writer::Serializer module, when given a series of maps which
8
+ share some keys and values. It is used to test that decoder code can handle
9
+ pointers to map keys and values, as well as to the whole map.
10
+
11
+ There are several ways to figure out what IP addresses are actually in the
12
+ test databases. You can take a look at the
13
+ [source-data directory](https://github.com/maxmind/MaxMind-DB/tree/master/source-data)
14
+ in this repository. This directory contains JSON files which are used to
15
+ generate many (but not all) of the database files.
16
+
17
+ You can also use the
18
+ [mmdb-dump-database script](https://github.com/maxmind/MaxMind-DB-Reader-perl/blob/master/eg/mmdb-dump-database)
19
+ in the
20
+ [MaxMind-DB-Reader-perl repository](https://github.com/maxmind/MaxMind-DB-Reader-perl).
21
+
22
+ Some databases are intentionally broken and cannot be dumped. You can look at
23
+ the
24
+ [script which generates these databases](https://github.com/maxmind/MaxMind-DB/blob/master/test-data/write-test-data.pl)
25
+ to see what IP addresses they include, which will be necessary for those
26
+ databases which cannot be dumped because they contain intentional errors.
@@ -0,0 +1,620 @@
1
+ #!/usr/bin/env perl
2
+
3
+ use strict;
4
+ use warnings;
5
+ use autodie;
6
+ use utf8;
7
+
8
+ use Carp qw( croak );
9
+ use Cwd qw( abs_path );
10
+ use File::Basename qw( dirname );
11
+ use File::Slurper qw( read_binary write_binary );
12
+ use Cpanel::JSON::XS qw( decode_json );
13
+ use Math::Int128 qw( uint128 );
14
+ use MaxMind::DB::Writer::Serializer 0.100004;
15
+ use MaxMind::DB::Writer::Tree 0.100004;
16
+ use MaxMind::DB::Writer::Util qw( key_for_data );
17
+ use Net::Works::Network;
18
+ use Test::MaxMind::DB::Common::Util qw( standard_test_metadata );
19
+
20
+ my $Dir = dirname( abs_path($0) );
21
+
22
+ sub main {
23
+ my @sizes = ( 24, 28, 32 );
24
+ my @ipv4_range = ( '1.1.1.1', '1.1.1.32' );
25
+
26
+ my @ipv4_subnets = Net::Works::Network->range_as_subnets(@ipv4_range);
27
+ for my $record_size (@sizes) {
28
+ write_test_db(
29
+ $record_size,
30
+ \@ipv4_subnets,
31
+ { ip_version => 4 },
32
+ 'ipv4',
33
+ );
34
+ }
35
+
36
+ write_broken_pointers_test_db(
37
+ 24,
38
+ \@ipv4_subnets,
39
+ { ip_version => 4 },
40
+ 'broken-pointers',
41
+ );
42
+
43
+ write_broken_search_tree_db(
44
+ 24,
45
+ \@ipv4_subnets,
46
+ { ip_version => 4 },
47
+ 'broken-search-tree',
48
+ );
49
+
50
+ my @ipv6_subnets = Net::Works::Network->range_as_subnets(
51
+ '::1:ffff:ffff',
52
+ '::2:0000:0059'
53
+ );
54
+
55
+ for my $record_size (@sizes) {
56
+ write_test_db(
57
+ $record_size,
58
+ \@ipv6_subnets,
59
+ { ip_version => 6 },
60
+ 'ipv6',
61
+ );
62
+
63
+ write_test_db(
64
+ $record_size,
65
+ [
66
+ @ipv6_subnets,
67
+ Net::Works::Network->range_as_subnets( @ipv4_range, 6 ),
68
+ ],
69
+ { ip_version => 6 },
70
+ 'mixed',
71
+ );
72
+ }
73
+
74
+ write_decoder_test_db();
75
+ write_deeply_nested_structures_db();
76
+
77
+ write_geoip2_dbs();
78
+ write_broken_geoip2_city_db();
79
+ write_invalid_node_count();
80
+
81
+ write_no_ipv4_tree_db();
82
+
83
+ write_no_map_db( \@ipv4_subnets );
84
+
85
+ write_test_serialization_data();
86
+
87
+ write_db_with_metadata_pointers();
88
+ }
89
+
90
+ sub write_broken_pointers_test_db {
91
+ no warnings 'redefine';
92
+
93
+ my $orig_store_data = MaxMind::DB::Writer::Serializer->can('store_data');
94
+
95
+ # This breaks the value of the record for the 1.1.1.32 network, causing it
96
+ # to point outside the database.
97
+ local *MaxMind::DB::Writer::Serializer::store_data = sub {
98
+ my $data_pointer = shift->$orig_store_data(@_);
99
+ my $value = $_[1];
100
+ if ( ref($value) eq 'HASH'
101
+ && exists $value->{ip}
102
+ && $value->{ip} eq '1.1.1.32' ) {
103
+
104
+ $data_pointer += 100_000;
105
+ }
106
+ return $data_pointer;
107
+ };
108
+
109
+ # The next hack will poison the data section for the 1.1.16/28 subnet
110
+ # value. It's value will be a pointer that resolves to an offset outside
111
+ # the database.
112
+
113
+ my $key_to_poison = key_for_data( { ip => '1.1.1.16' } );
114
+
115
+ my $orig_position_for_data
116
+ = MaxMind::DB::Writer::Serializer->can('_position_for_data');
117
+ local *MaxMind::DB::Writer::Serializer::_position_for_data = sub {
118
+ my $key = $_[1];
119
+
120
+ if ( $key eq $key_to_poison ) {
121
+ return 1_000_000;
122
+ }
123
+ else {
124
+ return shift->$orig_position_for_data(@_);
125
+ }
126
+ };
127
+
128
+ write_test_db(@_);
129
+
130
+ return;
131
+ }
132
+
133
+ sub write_broken_search_tree_db {
134
+ my $filename = ( write_test_db(@_) )[1];
135
+
136
+ my $content = read_binary($filename);
137
+
138
+ # This causes the right record of the first node to be 0, meaning it
139
+ # points back to the top of the tree. This should never happen in a
140
+ # database that follows the spec.
141
+ substr( $content, 5, 1 ) = "\0";
142
+ write_binary( $filename, $content );
143
+
144
+ return;
145
+ }
146
+
147
+ sub write_test_db {
148
+ my $record_size = shift;
149
+ my $subnets = shift;
150
+ my $metadata = shift;
151
+ my $ip_version_name = shift;
152
+
153
+ my $writer = MaxMind::DB::Writer::Tree->new(
154
+ ip_version => $subnets->[0]->version(),
155
+ record_size => $record_size,
156
+ alias_ipv6_to_ipv4 => ( $subnets->[0]->version() == 6 ? 1 : 0 ),
157
+ map_key_type_callback => sub { 'utf8_string' },
158
+ standard_test_metadata(),
159
+ %{$metadata},
160
+ );
161
+
162
+ for my $subnet ( @{$subnets} ) {
163
+ $writer->insert_network(
164
+ $subnet,
165
+ { ip => $subnet->first()->as_string() }
166
+ );
167
+ }
168
+
169
+ my $filename = sprintf(
170
+ "$Dir/MaxMind-DB-test-%s-%i.mmdb",
171
+ $ip_version_name,
172
+ $record_size,
173
+ );
174
+ open my $fh, '>', $filename;
175
+
176
+ $writer->write_tree($fh);
177
+
178
+ close $fh;
179
+
180
+ return ( $writer, $filename );
181
+ }
182
+
183
+ {
184
+ # We will store this once for each subnet so we will also be testing
185
+ # pointers, since the serializer will generate a pointer to this
186
+ # structure.
187
+ my %all_types = (
188
+ utf8_string => 'unicode! ☯ - ♫',
189
+ double => 42.123456,
190
+ bytes => pack( 'N', 42 ),
191
+ uint16 => 100,
192
+ uint32 => 2**28,
193
+ int32 => -1 * ( 2**28 ),
194
+ uint64 => uint128(1) << 60,
195
+ uint128 => uint128(1) << 120,
196
+ array => [ 1, 2, 3, ],
197
+ map => {
198
+ mapX => {
199
+ utf8_stringX => 'hello',
200
+ arrayX => [ 7, 8, 9 ],
201
+ },
202
+ },
203
+ boolean => 1,
204
+ float => 1.1,
205
+ );
206
+
207
+ my %all_types_0 = (
208
+ utf8_string => q{},
209
+ double => 0,
210
+ bytes => q{},
211
+ uint16 => 0,
212
+ uint32 => 0,
213
+ int32 => 0,
214
+ uint64 => uint128(0),
215
+ uint128 => uint128(0),
216
+ array => [],
217
+ map => {},
218
+ boolean => 0,
219
+ float => 0,
220
+ );
221
+
222
+ sub write_decoder_test_db {
223
+ my $writer = MaxMind::DB::Writer::Tree->new(
224
+ ip_version => 6,
225
+ record_size => 24,
226
+ database_type => 'MaxMind DB Decoder Test',
227
+ languages => ['en'],
228
+ description => {
229
+ en =>
230
+ 'MaxMind DB Decoder Test database - contains every MaxMind DB data type',
231
+ },
232
+ alias_ipv6_to_ipv4 => 1,
233
+ remove_reserved_networks => 0,
234
+ map_key_type_callback => sub {
235
+ my $key = $_[0];
236
+ $key =~ s/X$//;
237
+ return $key eq 'array' ? [ 'array', 'uint32' ] : $key;
238
+ },
239
+ );
240
+
241
+ my @subnets
242
+ = map { Net::Works::Network->new_from_string( string => $_ ) }
243
+ qw(
244
+ ::1.1.1.0/120
245
+ ::2.2.0.0/112
246
+ ::3.0.0.0/104
247
+ ::4.5.6.7/128
248
+ abcd::/64
249
+ 1000::1234:0000/112
250
+ );
251
+
252
+ for my $subnet (@subnets) {
253
+ $writer->insert_network(
254
+ $subnet,
255
+ \%all_types,
256
+ );
257
+ }
258
+
259
+ $writer->insert_network(
260
+ Net::Works::Network->new_from_string( string => '::0.0.0.0/128' ),
261
+ \%all_types_0,
262
+ );
263
+
264
+ open my $fh, '>', "$Dir/MaxMind-DB-test-decoder.mmdb";
265
+ $writer->write_tree($fh);
266
+ close $fh;
267
+
268
+ return;
269
+ }
270
+ }
271
+
272
+ {
273
+ my %nested = (
274
+ map1 => {
275
+ map2 => {
276
+ array => [
277
+ {
278
+ map3 => { a => 1, b => 2, c => 3 },
279
+ },
280
+ ],
281
+ },
282
+ },
283
+ );
284
+
285
+ sub write_deeply_nested_structures_db {
286
+ my $writer = MaxMind::DB::Writer::Tree->new(
287
+ ip_version => 6,
288
+ record_size => 24,
289
+ ip_version => 6,
290
+ database_type => 'MaxMind DB Nested Data Structures',
291
+ languages => ['en'],
292
+ description => {
293
+ en =>
294
+ 'MaxMind DB Nested Data Structures Test database - contains deeply nested map/array structures',
295
+ },
296
+ alias_ipv6_to_ipv4 => 1,
297
+ map_key_type_callback => sub {
298
+ my $key = shift;
299
+ return
300
+ $key =~ /^map/ ? 'map'
301
+ : $key eq 'array' ? [ 'array', 'map' ]
302
+ : 'uint32';
303
+ }
304
+ );
305
+
306
+ my @subnets
307
+ = map { Net::Works::Network->new_from_string( string => $_ ) }
308
+ qw(
309
+ ::1.1.1.0/120
310
+ ::2.2.0.0/112
311
+ ::3.0.0.0/104
312
+ ::4.5.6.7/128
313
+ abcd::/64
314
+ 1000::1234:0000/112
315
+ );
316
+
317
+ for my $subnet (@subnets) {
318
+ $writer->insert_network(
319
+ $subnet,
320
+ \%nested,
321
+ );
322
+ }
323
+
324
+ open my $fh, '>', "$Dir/MaxMind-DB-test-nested.mmdb";
325
+ $writer->write_tree($fh);
326
+ close $fh;
327
+
328
+ return;
329
+ }
330
+ }
331
+
332
+ sub write_geoip2_dbs {
333
+ _write_geoip2_db( @{$_}[ 0, 1 ], 'Test' )
334
+ for (
335
+ [ 'GeoIP2-Anonymous-IP', {} ],
336
+ ['GeoIP2-City'],
337
+ ['GeoIP2-Connection-Type'],
338
+ ['GeoIP2-Country'],
339
+ ['GeoIP2-DensityIncome'],
340
+ ['GeoIP2-Domain'],
341
+ ['GeoIP2-Enterprise'],
342
+ ['GeoIP2-ISP'],
343
+ ['GeoIP2-Precision-Enterprise'],
344
+ ['GeoIP2-User-Count'],
345
+ ['GeoLite2-ASN'],
346
+ );
347
+ }
348
+
349
+ sub write_broken_geoip2_city_db {
350
+ no warnings 'redefine';
351
+
352
+ # This is how we _used_ to encode doubles. Storing them this way with the
353
+ # current reader tools can lead to weird errors. This broken database is a
354
+ # good way to test the robustness of reader code in the face of broken
355
+ # databases.
356
+ local *MaxMind::DB::Writer::Serializer::_encode_double = sub {
357
+ my $self = shift;
358
+ my $value = shift;
359
+
360
+ $self->_simple_encode( double => $value );
361
+ };
362
+
363
+ _write_geoip2_db( 'GeoIP2-City', 0, 'Test Broken Double Format' );
364
+ }
365
+
366
+ sub write_invalid_node_count {
367
+ no warnings 'redefine';
368
+ local *MaxMind::DB::Writer::Tree::node_count = sub { 100000 };
369
+
370
+ _write_geoip2_db( 'GeoIP2-City', 0, 'Test Invalid Node Count' );
371
+ }
372
+
373
+ sub _universal_map_key_type_callback {
374
+ my $map = {
375
+
376
+ # languages
377
+ de => 'utf8_string',
378
+ en => 'utf8_string',
379
+ es => 'utf8_string',
380
+ fr => 'utf8_string',
381
+ ja => 'utf8_string',
382
+ 'pt-BR' => 'utf8_string',
383
+ ru => 'utf8_string',
384
+ 'zh-CN' => 'utf8_string',
385
+
386
+ # production
387
+ accuracy_radius => 'uint16',
388
+ autonomous_system_number => 'uint32',
389
+ autonomous_system_organization => 'utf8_string',
390
+ average_income => 'uint32',
391
+ city => 'map',
392
+ code => 'utf8_string',
393
+ confidence => 'uint16',
394
+ connection_type => 'utf8_string',
395
+ continent => 'map',
396
+ country => 'map',
397
+ domain => 'utf8_string',
398
+ geoname_id => 'uint32',
399
+ ipv4_24 => 'uint32',
400
+ ipv4_32 => 'uint32',
401
+ ipv6_32 => 'uint32',
402
+ ipv6_48 => 'uint32',
403
+ ipv6_64 => 'uint32',
404
+ is_anonymous => 'boolean',
405
+ is_anonymous_proxy => 'boolean',
406
+ is_anonymous_vpn => 'boolean',
407
+ is_hosting_provider => 'boolean',
408
+ is_in_european_union => 'boolean',
409
+ is_legitimate_proxy => 'boolean',
410
+ is_public_proxy => 'boolean',
411
+ is_satellite_provider => 'boolean',
412
+ is_tor_exit_node => 'boolean',
413
+ iso_code => 'utf8_string',
414
+ isp => 'utf8_string',
415
+ latitude => 'double',
416
+ location => 'map',
417
+ longitude => 'double',
418
+ metro_code => 'uint16',
419
+ names => 'map',
420
+ organization => 'utf8_string',
421
+ population_density => 'uint32',
422
+ postal => 'map',
423
+ registered_country => 'map',
424
+ represented_country => 'map',
425
+ subdivisions => [ 'array', 'map' ],
426
+ time_zone => 'utf8_string',
427
+ traits => 'map',
428
+ traits => 'map',
429
+ type => 'utf8_string',
430
+ user_type => 'utf8_string',
431
+
432
+ # for testing only
433
+ foo => 'utf8_string',
434
+ bar => 'utf8_string',
435
+ buzz => 'utf8_string',
436
+ our_value => 'utf8_string',
437
+ };
438
+
439
+ my $callback = sub {
440
+ my $key = shift;
441
+
442
+ return $map->{$key} || die <<"ERROR";
443
+ Unknown tree key '$key'.
444
+
445
+ The universal_map_key_type_callback doesn't know what type to use for the passed
446
+ key. If you are adding a new key that will be used in a frozen tree / mmdb then
447
+ you should update the mapping in both our internal code and here.
448
+ ERROR
449
+ };
450
+
451
+ return $callback;
452
+ }
453
+
454
+ sub _write_geoip2_db {
455
+ my $type = shift;
456
+ my $populate_all_networks_with_data = shift;
457
+ my $description = shift;
458
+
459
+ my $writer = MaxMind::DB::Writer::Tree->new(
460
+ ip_version => 6,
461
+ record_size => 28,
462
+ ip_version => 6,
463
+ database_type => $type,
464
+ languages => [ 'en', $type eq 'GeoIP2-City' ? ('zh') : () ],
465
+ description => {
466
+ en => ( $type =~ s/-/ /gr )
467
+ . " $description Database (fake GeoIP2 data, for example purposes only)",
468
+ $type eq 'GeoIP2-City' ? ( zh => '小型数据库' ) : (),
469
+ },
470
+ alias_ipv6_to_ipv4 => 1,
471
+ map_key_type_callback => _universal_map_key_type_callback(),
472
+ );
473
+
474
+ _populate_all_networks( $writer, $populate_all_networks_with_data )
475
+ if $populate_all_networks_with_data;
476
+
477
+ my $value = shift;
478
+ my $nodes
479
+ = decode_json( read_binary("$Dir/../source-data/$type-Test.json") );
480
+
481
+ for my $node (@$nodes) {
482
+ for my $network ( keys %$node ) {
483
+ $writer->insert_network(
484
+ Net::Works::Network->new_from_string( string => $network ),
485
+ $node->{$network}
486
+ );
487
+ }
488
+ }
489
+
490
+ my $suffix = $description =~ s/ /-/gr;
491
+ open my $output_fh, '>', "$Dir/$type-$suffix.mmdb";
492
+ $writer->write_tree($output_fh);
493
+ close $output_fh;
494
+
495
+ return;
496
+ }
497
+
498
+ sub _populate_all_networks {
499
+ my $writer = shift;
500
+ my $data = shift;
501
+
502
+ my $max_uint128 = uint128(0) - 1;
503
+ my @networks = Net::Works::Network->range_as_subnets(
504
+ Net::Works::Address->new_from_integer(
505
+ integer => 0,
506
+ version => 6,
507
+ ),
508
+ Net::Works::Address->new_from_integer(
509
+ integer => $max_uint128,
510
+ version => 6,
511
+ ),
512
+ );
513
+
514
+ for my $network (@networks) {
515
+ $writer->insert_network( $network => $data );
516
+ }
517
+ }
518
+
519
+ sub write_no_ipv4_tree_db {
520
+ my $subnets = shift;
521
+
522
+ my $writer = MaxMind::DB::Writer::Tree->new(
523
+ ip_version => 6,
524
+ record_size => 24,
525
+ ip_version => 6,
526
+ database_type => 'MaxMind DB No IPv4 Search Tree',
527
+ languages => ['en'],
528
+ description => {
529
+ en => 'MaxMind DB No IPv4 Search Tree',
530
+ },
531
+ remove_reserved_networks => 0,
532
+ root_data_type => 'utf8_string',
533
+ map_key_type_callback => sub { {} },
534
+ );
535
+
536
+ my $subnet = Net::Works::Network->new_from_string( string => '::/64' );
537
+ $writer->insert_network( $subnet, $subnet->as_string() );
538
+
539
+ open my $output_fh, '>', "$Dir/MaxMind-DB-no-ipv4-search-tree.mmdb";
540
+ $writer->write_tree($output_fh);
541
+ close $output_fh;
542
+
543
+ return;
544
+ }
545
+
546
+ # The point of this database is to provide something where we can test looking
547
+ # up a single value. In other words, each IP address points to a non-compound
548
+ # value, a string rather than a map or array.
549
+ sub write_no_map_db {
550
+ my $subnets = shift;
551
+
552
+ my $writer = MaxMind::DB::Writer::Tree->new(
553
+ ip_version => 4,
554
+ record_size => 24,
555
+ database_type => 'MaxMind DB String Value Entries',
556
+ languages => ['en'],
557
+ description => {
558
+ en =>
559
+ 'MaxMind DB String Value Entries (no maps or arrays as values)',
560
+ },
561
+ root_data_type => 'utf8_string',
562
+ map_key_type_callback => sub { {} },
563
+ );
564
+
565
+ for my $subnet ( @{$subnets} ) {
566
+ $writer->insert_network( $subnet, $subnet->as_string() );
567
+ }
568
+
569
+ open my $output_fh, '>', "$Dir/MaxMind-DB-string-value-entries.mmdb";
570
+ $writer->write_tree($output_fh);
571
+ close $output_fh;
572
+
573
+ return;
574
+ }
575
+
576
+ sub write_test_serialization_data {
577
+ my $serializer = MaxMind::DB::Writer::Serializer->new(
578
+ map_key_type_callback => sub { 'utf8_string' } );
579
+
580
+ $serializer->store_data( map => { long_key => 'long_value1' } );
581
+ $serializer->store_data( map => { long_key => 'long_value2' } );
582
+ $serializer->store_data( map => { long_key2 => 'long_value1' } );
583
+ $serializer->store_data( map => { long_key2 => 'long_value2' } );
584
+ $serializer->store_data( map => { long_key => 'long_value1' } );
585
+ $serializer->store_data( map => { long_key2 => 'long_value2' } );
586
+
587
+ open my $fh, '>', 'maps-with-pointers.raw';
588
+ print {$fh} ${ $serializer->buffer() }
589
+ or die "Cannot write to maps-with-pointers.raw: $!";
590
+ close $fh;
591
+
592
+ return;
593
+ }
594
+
595
+ sub write_db_with_metadata_pointers {
596
+ my $repeated_string = 'Lots of pointers in metadata';
597
+ my $writer = MaxMind::DB::Writer::Tree->new(
598
+ ip_version => 6,
599
+ record_size => 24,
600
+ map_key_type_callback => sub { 'utf8_string' },
601
+ database_type => $repeated_string,
602
+ languages => [ 'en', 'es', 'zh' ],
603
+ description => {
604
+ en => $repeated_string,
605
+ es => $repeated_string,
606
+ zh => $repeated_string,
607
+ },
608
+
609
+ );
610
+
611
+ _populate_all_networks( $writer, {} );
612
+
613
+ open my $fh, '>', 'MaxMind-DB-test-metadata-pointers.mmdb';
614
+
615
+ $writer->write_tree($fh);
616
+
617
+ close $fh;
618
+ }
619
+
620
+ main();