maxmind-geoip2 0.2.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +33 -0
  3. data/Gemfile +1 -7
  4. data/README.dev.md +1 -1
  5. data/README.md +36 -14
  6. data/lib/maxmind/geoip2/client.rb +289 -265
  7. data/lib/maxmind/geoip2/errors.rb +0 -7
  8. data/lib/maxmind/geoip2/model/abstract.rb +19 -15
  9. data/lib/maxmind/geoip2/model/anonymous_ip.rb +62 -50
  10. data/lib/maxmind/geoip2/model/asn.rb +33 -29
  11. data/lib/maxmind/geoip2/model/city.rb +59 -55
  12. data/lib/maxmind/geoip2/model/connection_type.rb +27 -23
  13. data/lib/maxmind/geoip2/model/country.rb +57 -53
  14. data/lib/maxmind/geoip2/model/domain.rb +27 -23
  15. data/lib/maxmind/geoip2/model/enterprise.rb +13 -9
  16. data/lib/maxmind/geoip2/model/insights.rb +12 -8
  17. data/lib/maxmind/geoip2/model/isp.rb +45 -41
  18. data/lib/maxmind/geoip2/reader.rb +260 -233
  19. data/lib/maxmind/geoip2/record/abstract.rb +17 -13
  20. data/lib/maxmind/geoip2/record/city.rb +33 -29
  21. data/lib/maxmind/geoip2/record/continent.rb +32 -28
  22. data/lib/maxmind/geoip2/record/country.rb +47 -43
  23. data/lib/maxmind/geoip2/record/location.rb +64 -60
  24. data/lib/maxmind/geoip2/record/maxmind.rb +14 -10
  25. data/lib/maxmind/geoip2/record/place.rb +22 -18
  26. data/lib/maxmind/geoip2/record/postal.rb +26 -22
  27. data/lib/maxmind/geoip2/record/represented_country.rb +20 -16
  28. data/lib/maxmind/geoip2/record/subdivision.rb +42 -38
  29. data/lib/maxmind/geoip2/record/traits.rb +204 -191
  30. data/maxmind-geoip2.gemspec +10 -3
  31. data/test/data/LICENSE +4 -0
  32. data/test/data/MaxMind-DB-spec.md +570 -0
  33. data/test/data/MaxMind-DB-test-metadata-pointers.mmdb +0 -0
  34. data/test/data/README.md +4 -0
  35. data/test/data/bad-data/README.md +7 -0
  36. data/test/data/bad-data/libmaxminddb/libmaxminddb-offset-integer-overflow.mmdb +0 -0
  37. data/test/data/bad-data/maxminddb-golang/cyclic-data-structure.mmdb +0 -0
  38. data/test/data/bad-data/maxminddb-golang/invalid-bytes-length.mmdb +1 -0
  39. data/test/data/bad-data/maxminddb-golang/invalid-data-record-offset.mmdb +0 -0
  40. data/test/data/bad-data/maxminddb-golang/invalid-map-key-length.mmdb +0 -0
  41. data/test/data/bad-data/maxminddb-golang/invalid-string-length.mmdb +1 -0
  42. data/test/data/bad-data/maxminddb-golang/metadata-is-an-uint128.mmdb +1 -0
  43. data/test/data/bad-data/maxminddb-golang/unexpected-bytes.mmdb +0 -0
  44. data/test/data/bad-data/maxminddb-python/bad-unicode-in-map-key.mmdb +0 -0
  45. data/test/data/perltidyrc +12 -0
  46. data/test/data/source-data/GeoIP2-Anonymous-IP-Test.json +49 -0
  47. data/test/data/source-data/GeoIP2-City-Test.json +12852 -0
  48. data/test/data/source-data/GeoIP2-Connection-Type-Test.json +102 -0
  49. data/test/data/source-data/GeoIP2-Country-Test.json +15916 -0
  50. data/test/data/source-data/GeoIP2-DensityIncome-Test.json +14 -0
  51. data/test/data/source-data/GeoIP2-Domain-Test.json +452 -0
  52. data/test/data/source-data/GeoIP2-Enterprise-Test.json +687 -0
  53. data/test/data/source-data/GeoIP2-ISP-Test.json +12595 -0
  54. data/test/data/source-data/GeoIP2-Precision-Enterprise-Test.json +2148 -0
  55. data/test/data/source-data/GeoIP2-Static-IP-Score-Test.json +2132 -0
  56. data/test/data/source-data/GeoIP2-User-Count-Test.json +2837 -0
  57. data/test/data/source-data/GeoLite2-ASN-Test.json +37 -0
  58. data/test/data/source-data/README +15 -0
  59. data/test/data/test-data/GeoIP2-Anonymous-IP-Test.mmdb +0 -0
  60. data/test/data/test-data/GeoIP2-City-Test-Broken-Double-Format.mmdb +0 -0
  61. data/test/data/test-data/GeoIP2-City-Test-Invalid-Node-Count.mmdb +0 -0
  62. data/test/data/test-data/GeoIP2-City-Test.mmdb +0 -0
  63. data/test/data/test-data/GeoIP2-Connection-Type-Test.mmdb +0 -0
  64. data/test/data/test-data/GeoIP2-Country-Test.mmdb +0 -0
  65. data/test/data/test-data/GeoIP2-DensityIncome-Test.mmdb +0 -0
  66. data/test/data/test-data/GeoIP2-Domain-Test.mmdb +0 -0
  67. data/test/data/test-data/GeoIP2-Enterprise-Test.mmdb +0 -0
  68. data/test/data/test-data/GeoIP2-ISP-Test.mmdb +0 -0
  69. data/test/data/test-data/GeoIP2-Precision-Enterprise-Test.mmdb +0 -0
  70. data/test/data/test-data/GeoIP2-Static-IP-Score-Test.mmdb +0 -0
  71. data/test/data/test-data/GeoIP2-User-Count-Test.mmdb +0 -0
  72. data/test/data/test-data/GeoLite2-ASN-Test.mmdb +0 -0
  73. data/test/data/test-data/MaxMind-DB-no-ipv4-search-tree.mmdb +0 -0
  74. data/test/data/test-data/MaxMind-DB-string-value-entries.mmdb +0 -0
  75. data/test/data/test-data/MaxMind-DB-test-broken-pointers-24.mmdb +0 -0
  76. data/test/data/test-data/MaxMind-DB-test-broken-search-tree-24.mmdb +0 -0
  77. data/test/data/test-data/MaxMind-DB-test-decoder.mmdb +0 -0
  78. data/test/data/test-data/MaxMind-DB-test-ipv4-24.mmdb +0 -0
  79. data/test/data/test-data/MaxMind-DB-test-ipv4-28.mmdb +0 -0
  80. data/test/data/test-data/MaxMind-DB-test-ipv4-32.mmdb +0 -0
  81. data/test/data/test-data/MaxMind-DB-test-ipv6-24.mmdb +0 -0
  82. data/test/data/test-data/MaxMind-DB-test-ipv6-28.mmdb +0 -0
  83. data/test/data/test-data/MaxMind-DB-test-ipv6-32.mmdb +0 -0
  84. data/test/data/test-data/MaxMind-DB-test-metadata-pointers.mmdb +0 -0
  85. data/test/data/test-data/MaxMind-DB-test-mixed-24.mmdb +0 -0
  86. data/test/data/test-data/MaxMind-DB-test-mixed-28.mmdb +0 -0
  87. data/test/data/test-data/MaxMind-DB-test-mixed-32.mmdb +0 -0
  88. data/test/data/test-data/MaxMind-DB-test-nested.mmdb +0 -0
  89. data/test/data/test-data/MaxMind-DB-test-pointer-decoder.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 +691 -0
  93. data/test/data/tidyall.ini +5 -0
  94. data/test/test_client.rb +4 -2
  95. data/test/test_model_country.rb +16 -0
  96. data/test/test_reader.rb +59 -0
  97. metadata +156 -10
  98. data/Gemfile.lock +0 -70
@@ -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,691 @@
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_pointer_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
+ # We limit this to numeric types as the other types would generate
223
+ # very large databases
224
+ my %numeric_types_max = (
225
+ double => 'Inf',
226
+ float => 'Inf',
227
+ int32 => 0x7fffffff,
228
+ uint16 => 0xffff,
229
+ uint32 => string_to_uint128('0xffff_ffff'),
230
+ uint64 => string_to_uint128('0xffff_ffff_ffff_ffff'),
231
+ uint128 => MAX_UINT128,
232
+ );
233
+
234
+ sub write_decoder_test_db {
235
+ my $writer = _decoder_writer();
236
+
237
+ my @subnets
238
+ = map { Net::Works::Network->new_from_string( string => $_ ) }
239
+ qw(
240
+ ::1.1.1.0/120
241
+ ::2.2.0.0/112
242
+ ::3.0.0.0/104
243
+ ::4.5.6.7/128
244
+ abcd::/64
245
+ 1000::1234:0000/112
246
+ );
247
+
248
+ for my $subnet (@subnets) {
249
+ $writer->insert_network(
250
+ $subnet,
251
+ \%all_types,
252
+ );
253
+ }
254
+
255
+ $writer->insert_network(
256
+ Net::Works::Network->new_from_string( string => '::0.0.0.0/128' ),
257
+ \%all_types_0,
258
+ );
259
+
260
+ $writer->insert_network(
261
+ Net::Works::Network->new_from_string(
262
+ string => '::255.255.255.255/128'
263
+ ),
264
+ \%numeric_types_max,
265
+ );
266
+
267
+ open my $fh, '>', "$Dir/MaxMind-DB-test-decoder.mmdb";
268
+ $writer->write_tree($fh);
269
+ close $fh;
270
+
271
+ return;
272
+ }
273
+
274
+ sub write_pointer_decoder_test_db {
275
+
276
+ # We want to create a database where most values are pointers
277
+ no warnings 'redefine';
278
+ local *MaxMind::DB::Writer::Serializer::_should_cache_value
279
+ = sub { 1 };
280
+ my $writer = _decoder_writer();
281
+
282
+ # We add these slightly different records so that we end up with
283
+ # pointers for the individual values in the maps, not just pointers
284
+ # to the map
285
+ $writer->insert_network(
286
+ '1.0.0.0/32',
287
+ {
288
+ %all_types,
289
+ booleanX => 0,
290
+ arrayX => [ 1, 2, 3, 4, ],
291
+ mapXX => {
292
+ utf8_stringX => 'hello',
293
+ arrayX => [ 7, 8, 9, 10 ],
294
+ booleanX => 0,
295
+ },
296
+ },
297
+ );
298
+
299
+ $writer->insert_network(
300
+ '1.1.1.0/32',
301
+ {
302
+ %all_types,
303
+
304
+ # This has to be 0 rather than 1 as otherwise the buggy
305
+ # Perl writer will think it is the same as an uint32 value of
306
+ # 1 and make a pointer to a value of a different type.
307
+ boolean => 0,
308
+ },
309
+ );
310
+
311
+ open my $fh, '>', "$Dir/MaxMind-DB-test-pointer-decoder.mmdb";
312
+ $writer->write_tree($fh);
313
+ close $fh;
314
+
315
+ return;
316
+ }
317
+
318
+ sub _decoder_writer {
319
+ return MaxMind::DB::Writer::Tree->new(
320
+ ip_version => 6,
321
+ record_size => 24,
322
+ database_type => 'MaxMind DB Decoder Test',
323
+ languages => ['en'],
324
+ description => {
325
+ en =>
326
+ 'MaxMind DB Decoder Test database - contains every MaxMind DB data type',
327
+ },
328
+ alias_ipv6_to_ipv4 => 1,
329
+ remove_reserved_networks => 0,
330
+ map_key_type_callback => sub {
331
+ my $key = $_[0];
332
+ $key =~ s/X*$//;
333
+ return $key eq 'array' ? [ 'array', 'uint32' ] : $key;
334
+ },
335
+ );
336
+ }
337
+ }
338
+
339
+ {
340
+ my %nested = (
341
+ map1 => {
342
+ map2 => {
343
+ array => [
344
+ {
345
+ map3 => { a => 1, b => 2, c => 3 },
346
+ },
347
+ ],
348
+ },
349
+ },
350
+ );
351
+
352
+ sub write_deeply_nested_structures_db {
353
+ my $writer = MaxMind::DB::Writer::Tree->new(
354
+ ip_version => 6,
355
+ record_size => 24,
356
+ ip_version => 6,
357
+ database_type => 'MaxMind DB Nested Data Structures',
358
+ languages => ['en'],
359
+ description => {
360
+ en =>
361
+ 'MaxMind DB Nested Data Structures Test database - contains deeply nested map/array structures',
362
+ },
363
+ alias_ipv6_to_ipv4 => 1,
364
+ map_key_type_callback => sub {
365
+ my $key = shift;
366
+ return
367
+ $key =~ /^map/ ? 'map'
368
+ : $key eq 'array' ? [ 'array', 'map' ]
369
+ : 'uint32';
370
+ }
371
+ );
372
+
373
+ my @subnets
374
+ = map { Net::Works::Network->new_from_string( string => $_ ) }
375
+ qw(
376
+ ::1.1.1.0/120
377
+ ::2.2.0.0/112
378
+ ::3.0.0.0/104
379
+ ::4.5.6.7/128
380
+ abcd::/64
381
+ 1000::1234:0000/112
382
+ );
383
+
384
+ for my $subnet (@subnets) {
385
+ $writer->insert_network(
386
+ $subnet,
387
+ \%nested,
388
+ );
389
+ }
390
+
391
+ open my $fh, '>', "$Dir/MaxMind-DB-test-nested.mmdb";
392
+ $writer->write_tree($fh);
393
+ close $fh;
394
+
395
+ return;
396
+ }
397
+ }
398
+
399
+ sub write_geoip2_dbs {
400
+ _write_geoip2_db( @{$_}[ 0, 1 ], 'Test' )
401
+ for (
402
+ [ 'GeoIP2-Anonymous-IP', {} ],
403
+ ['GeoIP2-City'],
404
+ ['GeoIP2-Connection-Type'],
405
+ ['GeoIP2-Country'],
406
+ ['GeoIP2-DensityIncome'],
407
+ ['GeoIP2-Domain'],
408
+ ['GeoIP2-Enterprise'],
409
+ ['GeoIP2-ISP'],
410
+ ['GeoIP2-Precision-Enterprise'],
411
+ ['GeoIP2-Static-IP-Score'],
412
+ ['GeoIP2-User-Count'],
413
+ ['GeoLite2-ASN'],
414
+ );
415
+ }
416
+
417
+ sub write_broken_geoip2_city_db {
418
+ no warnings 'redefine';
419
+
420
+ # This is how we _used_ to encode doubles. Storing them this way with the
421
+ # current reader tools can lead to weird errors. This broken database is a
422
+ # good way to test the robustness of reader code in the face of broken
423
+ # databases.
424
+ local *MaxMind::DB::Writer::Serializer::_encode_double = sub {
425
+ my $self = shift;
426
+ my $value = shift;
427
+
428
+ $self->_simple_encode( double => $value );
429
+ };
430
+
431
+ _write_geoip2_db( 'GeoIP2-City', 0, 'Test Broken Double Format' );
432
+ }
433
+
434
+ sub write_invalid_node_count {
435
+ no warnings 'redefine';
436
+ local *MaxMind::DB::Writer::Tree::node_count = sub { 100000 };
437
+
438
+ _write_geoip2_db( 'GeoIP2-City', 0, 'Test Invalid Node Count' );
439
+ }
440
+
441
+ sub _universal_map_key_type_callback {
442
+ my $map = {
443
+
444
+ # languages
445
+ de => 'utf8_string',
446
+ en => 'utf8_string',
447
+ es => 'utf8_string',
448
+ fr => 'utf8_string',
449
+ ja => 'utf8_string',
450
+ 'pt-BR' => 'utf8_string',
451
+ ru => 'utf8_string',
452
+ 'zh-CN' => 'utf8_string',
453
+
454
+ # production
455
+ accuracy_radius => 'uint16',
456
+ autonomous_system_number => 'uint32',
457
+ autonomous_system_organization => 'utf8_string',
458
+ average_income => 'uint32',
459
+ city => 'map',
460
+ code => 'utf8_string',
461
+ confidence => 'uint16',
462
+ connection_type => 'utf8_string',
463
+ continent => 'map',
464
+ country => 'map',
465
+ domain => 'utf8_string',
466
+ geoname_id => 'uint32',
467
+ ipv4_24 => 'uint32',
468
+ ipv4_32 => 'uint32',
469
+ ipv6_32 => 'uint32',
470
+ ipv6_48 => 'uint32',
471
+ ipv6_64 => 'uint32',
472
+ is_anonymous => 'boolean',
473
+ is_anonymous_proxy => 'boolean',
474
+ is_anonymous_vpn => 'boolean',
475
+ is_hosting_provider => 'boolean',
476
+ is_in_european_union => 'boolean',
477
+ is_legitimate_proxy => 'boolean',
478
+ is_public_proxy => 'boolean',
479
+ is_residential_proxy => 'boolean',
480
+ is_satellite_provider => 'boolean',
481
+ is_tor_exit_node => 'boolean',
482
+ iso_code => 'utf8_string',
483
+ isp => 'utf8_string',
484
+ latitude => 'double',
485
+ location => 'map',
486
+ longitude => 'double',
487
+ metro_code => 'uint16',
488
+ names => 'map',
489
+ organization => 'utf8_string',
490
+ population_density => 'uint32',
491
+ postal => 'map',
492
+ registered_country => 'map',
493
+ represented_country => 'map',
494
+ score => 'double',
495
+ static_ip_score => 'double',
496
+ subdivisions => [ 'array', 'map' ],
497
+ time_zone => 'utf8_string',
498
+ traits => 'map',
499
+ traits => 'map',
500
+ type => 'utf8_string',
501
+ user_type => 'utf8_string',
502
+
503
+ # for testing only
504
+ foo => 'utf8_string',
505
+ bar => 'utf8_string',
506
+ buzz => 'utf8_string',
507
+ our_value => 'utf8_string',
508
+ };
509
+
510
+ my $callback = sub {
511
+ my $key = shift;
512
+
513
+ return $map->{$key} || die <<"ERROR";
514
+ Unknown tree key '$key'.
515
+
516
+ The universal_map_key_type_callback doesn't know what type to use for the passed
517
+ key. If you are adding a new key that will be used in a frozen tree / mmdb then
518
+ you should update the mapping in both our internal code and here.
519
+ ERROR
520
+ };
521
+
522
+ return $callback;
523
+ }
524
+
525
+ sub _write_geoip2_db {
526
+ my $type = shift;
527
+ my $populate_all_networks_with_data = shift;
528
+ my $description = shift;
529
+
530
+ my $writer = MaxMind::DB::Writer::Tree->new(
531
+ ip_version => 6,
532
+ record_size => 28,
533
+ ip_version => 6,
534
+ database_type => $type,
535
+ languages => [ 'en', $type eq 'GeoIP2-City' ? ('zh') : () ],
536
+ description => {
537
+ en => ( $type =~ s/-/ /gr )
538
+ . " $description Database (fake GeoIP2 data, for example purposes only)",
539
+ $type eq 'GeoIP2-City' ? ( zh => '小型数据库' ) : (),
540
+ },
541
+ alias_ipv6_to_ipv4 => 1,
542
+ map_key_type_callback => _universal_map_key_type_callback(),
543
+ );
544
+
545
+ _populate_all_networks( $writer, $populate_all_networks_with_data )
546
+ if $populate_all_networks_with_data;
547
+
548
+ my $value = shift;
549
+ my $nodes
550
+ = decode_json( read_binary("$Dir/../source-data/$type-Test.json") );
551
+
552
+ for my $node (@$nodes) {
553
+ for my $network ( keys %$node ) {
554
+ $writer->insert_network(
555
+ Net::Works::Network->new_from_string( string => $network ),
556
+ $node->{$network}
557
+ );
558
+ }
559
+ }
560
+
561
+ my $suffix = $description =~ s/ /-/gr;
562
+ open my $output_fh, '>', "$Dir/$type-$suffix.mmdb";
563
+ $writer->write_tree($output_fh);
564
+ close $output_fh;
565
+
566
+ return;
567
+ }
568
+
569
+ sub _populate_all_networks {
570
+ my $writer = shift;
571
+ my $data = shift;
572
+
573
+ my $max_uint128 = uint128(0) - 1;
574
+ my @networks = Net::Works::Network->range_as_subnets(
575
+ Net::Works::Address->new_from_integer(
576
+ integer => 0,
577
+ version => 6,
578
+ ),
579
+ Net::Works::Address->new_from_integer(
580
+ integer => $max_uint128,
581
+ version => 6,
582
+ ),
583
+ );
584
+
585
+ for my $network (@networks) {
586
+ $writer->insert_network( $network => $data );
587
+ }
588
+ }
589
+
590
+ sub write_no_ipv4_tree_db {
591
+ my $subnets = shift;
592
+
593
+ my $writer = MaxMind::DB::Writer::Tree->new(
594
+ ip_version => 6,
595
+ record_size => 24,
596
+ ip_version => 6,
597
+ database_type => 'MaxMind DB No IPv4 Search Tree',
598
+ languages => ['en'],
599
+ description => {
600
+ en => 'MaxMind DB No IPv4 Search Tree',
601
+ },
602
+ remove_reserved_networks => 0,
603
+ root_data_type => 'utf8_string',
604
+ map_key_type_callback => sub { {} },
605
+ );
606
+
607
+ my $subnet = Net::Works::Network->new_from_string( string => '::/64' );
608
+ $writer->insert_network( $subnet, $subnet->as_string() );
609
+
610
+ open my $output_fh, '>', "$Dir/MaxMind-DB-no-ipv4-search-tree.mmdb";
611
+ $writer->write_tree($output_fh);
612
+ close $output_fh;
613
+
614
+ return;
615
+ }
616
+
617
+ # The point of this database is to provide something where we can test looking
618
+ # up a single value. In other words, each IP address points to a non-compound
619
+ # value, a string rather than a map or array.
620
+ sub write_no_map_db {
621
+ my $subnets = shift;
622
+
623
+ my $writer = MaxMind::DB::Writer::Tree->new(
624
+ ip_version => 4,
625
+ record_size => 24,
626
+ database_type => 'MaxMind DB String Value Entries',
627
+ languages => ['en'],
628
+ description => {
629
+ en =>
630
+ 'MaxMind DB String Value Entries (no maps or arrays as values)',
631
+ },
632
+ root_data_type => 'utf8_string',
633
+ map_key_type_callback => sub { {} },
634
+ );
635
+
636
+ for my $subnet ( @{$subnets} ) {
637
+ $writer->insert_network( $subnet, $subnet->as_string() );
638
+ }
639
+
640
+ open my $output_fh, '>', "$Dir/MaxMind-DB-string-value-entries.mmdb";
641
+ $writer->write_tree($output_fh);
642
+ close $output_fh;
643
+
644
+ return;
645
+ }
646
+
647
+ sub write_test_serialization_data {
648
+ my $serializer = MaxMind::DB::Writer::Serializer->new(
649
+ map_key_type_callback => sub { 'utf8_string' } );
650
+
651
+ $serializer->store_data( map => { long_key => 'long_value1' } );
652
+ $serializer->store_data( map => { long_key => 'long_value2' } );
653
+ $serializer->store_data( map => { long_key2 => 'long_value1' } );
654
+ $serializer->store_data( map => { long_key2 => 'long_value2' } );
655
+ $serializer->store_data( map => { long_key => 'long_value1' } );
656
+ $serializer->store_data( map => { long_key2 => 'long_value2' } );
657
+
658
+ open my $fh, '>', 'maps-with-pointers.raw';
659
+ print {$fh} ${ $serializer->buffer() }
660
+ or die "Cannot write to maps-with-pointers.raw: $!";
661
+ close $fh;
662
+
663
+ return;
664
+ }
665
+
666
+ sub write_db_with_metadata_pointers {
667
+ my $repeated_string = 'Lots of pointers in metadata';
668
+ my $writer = MaxMind::DB::Writer::Tree->new(
669
+ ip_version => 6,
670
+ record_size => 24,
671
+ map_key_type_callback => sub { 'utf8_string' },
672
+ database_type => $repeated_string,
673
+ languages => [ 'en', 'es', 'zh' ],
674
+ description => {
675
+ en => $repeated_string,
676
+ es => $repeated_string,
677
+ zh => $repeated_string,
678
+ },
679
+
680
+ );
681
+
682
+ _populate_all_networks( $writer, {} );
683
+
684
+ open my $fh, '>', 'MaxMind-DB-test-metadata-pointers.mmdb';
685
+
686
+ $writer->write_tree($fh);
687
+
688
+ close $fh;
689
+ }
690
+
691
+ main();