maxmind-geoip2 0.1.0

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