maxmind-geoip2 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -0
  3. data/README.md +13 -10
  4. data/lib/maxmind/geoip2/client.rb +18 -9
  5. data/lib/maxmind/geoip2/errors.rb +17 -8
  6. data/lib/maxmind/geoip2/model/city.rb +4 -5
  7. data/lib/maxmind/geoip2/model/connection_type.rb +3 -2
  8. data/lib/maxmind/geoip2/model/enterprise.rb +2 -3
  9. data/lib/maxmind/geoip2/model/insights.rb +3 -5
  10. data/lib/maxmind/geoip2/model/isp.rb +16 -0
  11. data/lib/maxmind/geoip2/reader.rb +1 -2
  12. data/lib/maxmind/geoip2/record/traits.rb +58 -26
  13. data/lib/maxmind/geoip2/version.rb +8 -0
  14. data/maxmind-geoip2.gemspec +8 -2
  15. data/test/data/MaxMind-DB-spec.md +15 -11
  16. data/test/data/cmd/write-test-data/main.go +68 -0
  17. data/test/data/go.mod +13 -0
  18. data/test/data/go.sum +16 -0
  19. data/test/data/perltidyrc +6 -0
  20. data/test/data/pkg/writer/decoder.go +178 -0
  21. data/test/data/pkg/writer/geoip2.go +182 -0
  22. data/test/data/pkg/writer/ip.go +39 -0
  23. data/test/data/pkg/writer/maxmind.go +245 -0
  24. data/test/data/pkg/writer/nestedstructures.go +73 -0
  25. data/test/data/pkg/writer/writer.go +58 -0
  26. data/test/data/source-data/GeoIP2-City-Test.json +402 -48
  27. data/test/data/source-data/GeoIP2-Connection-Type-Test.json +35 -10
  28. data/test/data/source-data/GeoIP2-Country-Test.json +145 -58
  29. data/test/data/source-data/GeoIP2-Domain-Test.json +5 -0
  30. data/test/data/source-data/GeoIP2-Enterprise-Test.json +408 -4
  31. data/test/data/source-data/GeoIP2-ISP-Test.json +10 -0
  32. data/test/data/source-data/GeoIP2-Precision-Enterprise-Sandbox-Test.json +296 -0
  33. data/test/data/source-data/GeoIP2-Precision-Enterprise-Test.json +473 -6
  34. data/test/data/source-data/GeoIP2-Static-IP-Score-Test.json +15 -0
  35. data/test/data/source-data/GeoIP2-User-Count-Test.json +18 -0
  36. data/test/data/source-data/GeoLite2-ASN-Test.json +4091 -8
  37. data/test/data/source-data/GeoLite2-City-Test.json +12972 -0
  38. data/test/data/source-data/GeoLite2-Country-Test.json +11372 -0
  39. data/test/data/test-data/GeoIP2-Anonymous-IP-Test.mmdb +0 -0
  40. data/test/data/test-data/GeoIP2-City-Test-Broken-Double-Format.mmdb +0 -0
  41. data/test/data/test-data/GeoIP2-City-Test-Invalid-Node-Count.mmdb +0 -0
  42. data/test/data/test-data/GeoIP2-City-Test.mmdb +0 -0
  43. data/test/data/test-data/GeoIP2-Connection-Type-Test.mmdb +0 -0
  44. data/test/data/test-data/GeoIP2-Country-Test.mmdb +0 -0
  45. data/test/data/test-data/GeoIP2-DensityIncome-Test.mmdb +0 -0
  46. data/test/data/test-data/GeoIP2-Domain-Test.mmdb +0 -0
  47. data/test/data/test-data/GeoIP2-Enterprise-Test.mmdb +0 -0
  48. data/test/data/test-data/GeoIP2-ISP-Test.mmdb +0 -0
  49. data/test/data/test-data/GeoIP2-Precision-Enterprise-Test.mmdb +0 -0
  50. data/test/data/test-data/GeoIP2-Static-IP-Score-Test.mmdb +0 -0
  51. data/test/data/test-data/GeoIP2-User-Count-Test.mmdb +0 -0
  52. data/test/data/test-data/GeoLite2-ASN-Test.mmdb +0 -0
  53. data/test/data/test-data/GeoLite2-City-Test.mmdb +0 -0
  54. data/test/data/test-data/GeoLite2-Country-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/MaxMind-DB-test-pointer-decoder.mmdb +0 -0
  72. data/test/data/test-data/README.md +30 -14
  73. data/test/test_client.rb +18 -2
  74. data/test/test_reader.rb +37 -3
  75. metadata +21 -8
  76. data/test/data/MaxMind-DB-test-metadata-pointers.mmdb +0 -0
  77. data/test/data/source-data/README +0 -15
  78. data/test/data/test-data/write-test-data.pl +0 -691
@@ -0,0 +1,245 @@
1
+ package writer
2
+
3
+ import (
4
+ "fmt"
5
+ "net/netip"
6
+
7
+ "github.com/maxmind/mmdbwriter"
8
+ "github.com/maxmind/mmdbwriter/mmdbtype"
9
+ "go4.org/netipx"
10
+ )
11
+
12
+ // WriteIPv4TestDB writes mmdb files for an ip range between 1.1.1.1 and 1.1.1.32
13
+ // with various record sizes.
14
+ func (w *Writer) WriteIPv4TestDB() error {
15
+ ipRange, err := parseIPRange("1.1.1.1", "1.1.1.32")
16
+ if err != nil {
17
+ return fmt.Errorf("parsing ip range: %w", err)
18
+ }
19
+
20
+ for _, recordSize := range []int{24, 28, 32} {
21
+ err := w.writeMaxMindTestDB(
22
+ recordSize,
23
+ []netipx.IPRange{ipRange},
24
+ "ipv4",
25
+ )
26
+ if err != nil {
27
+ return fmt.Errorf("writing test database: %w", err)
28
+ }
29
+ }
30
+
31
+ return nil
32
+ }
33
+
34
+ // WriteIPv6TestDB writes mmdb files for an ip range between ::1:ffff:ffff and ::2:0000:0059
35
+ // with various record sizes.
36
+ func (w *Writer) WriteIPv6TestDB() error {
37
+ ipRange, err := parseIPRange("::1:ffff:ffff", "::2:0000:0059")
38
+ if err != nil {
39
+ return fmt.Errorf("parsing ip range: %w", err)
40
+ }
41
+
42
+ for _, recordSize := range []int{24, 28, 32} {
43
+ err := w.writeMaxMindTestDB(
44
+ recordSize,
45
+ []netipx.IPRange{ipRange},
46
+ "ipv6",
47
+ )
48
+ if err != nil {
49
+ return fmt.Errorf("writing test database: %w", err)
50
+ }
51
+ }
52
+
53
+ return nil
54
+ }
55
+
56
+ // WriteMixedIPTestDB writes mmdb files for a mixed ip version range between ::1:ffff:ffff and ::2:0000:0059
57
+ // with various record sizes.
58
+ func (w *Writer) WriteMixedIPTestDB() error {
59
+ ipv6Range, err := parseIPRange("::1:ffff:ffff", "::2:0000:0059")
60
+ if err != nil {
61
+ return fmt.Errorf("parsing ip range: %w", err)
62
+ }
63
+
64
+ ipv4Range, err := parseIPRange("1.1.1.1", "1.1.1.32")
65
+ if err != nil {
66
+ return fmt.Errorf("parsing ip range: %w", err)
67
+ }
68
+
69
+ for _, recordSize := range []int{24, 28, 32} {
70
+ err := w.writeMaxMindTestDB(
71
+ recordSize,
72
+ []netipx.IPRange{ipv6Range, ipv4Range},
73
+ "mixed",
74
+ )
75
+ if err != nil {
76
+ return fmt.Errorf("writing test database: %w", err)
77
+ }
78
+ }
79
+
80
+ return nil
81
+ }
82
+
83
+ // writeMaxMindTestDB writes test mmdb files.
84
+ func (w *Writer) writeMaxMindTestDB(
85
+ recordSize int,
86
+ ipRange []netipx.IPRange,
87
+ ipVersionName string,
88
+ ) error {
89
+ ipVersion := 6
90
+ if ipRange[0].From().Is4() {
91
+ ipVersion = 4
92
+ }
93
+
94
+ metadata := map[string]string{}
95
+ metadata["en"] = "Test Database"
96
+ metadata["zh"] = "Test Database Chinese"
97
+
98
+ dbWriter, err := mmdbwriter.New(
99
+ mmdbwriter.Options{
100
+ DatabaseType: "Test",
101
+ Description: metadata,
102
+ DisableIPv4Aliasing: ipVersion == 4,
103
+ IPVersion: ipVersion,
104
+ Languages: []string{"en", "zh"},
105
+ RecordSize: recordSize,
106
+ },
107
+ )
108
+ if err != nil {
109
+ return fmt.Errorf("creating mmdbwriter: %w", err)
110
+ }
111
+
112
+ for _, ir := range ipRange {
113
+ for _, prefix := range ir.Prefixes() {
114
+ ipString := prefix.Addr().String()
115
+ if ipVersion == 6 && prefix.Addr().Is4() {
116
+ ipString = "::" + ipString
117
+ }
118
+
119
+ err := dbWriter.Insert(
120
+ netipx.PrefixIPNet(prefix),
121
+ mmdbtype.Map{
122
+ "ip": mmdbtype.String(ipString),
123
+ },
124
+ )
125
+ if err != nil {
126
+ return fmt.Errorf("inserting ip: %w", err)
127
+ }
128
+ }
129
+ }
130
+
131
+ fileName := fmt.Sprintf("MaxMind-DB-test-%s-%d.mmdb", ipVersionName, recordSize)
132
+ if err := w.write(dbWriter, fileName); err != nil {
133
+ return fmt.Errorf("writing database: %w", err)
134
+ }
135
+
136
+ return nil
137
+ }
138
+
139
+ // WriteNoIPv4TestDB writes an mmdb file with no ipv4 records.
140
+ func (w *Writer) WriteNoIPv4TestDB() error {
141
+ dbWriter, err := mmdbwriter.New(
142
+ mmdbwriter.Options{
143
+ DatabaseType: "MaxMind DB No IPv4 Search Tree",
144
+ Description: map[string]string{
145
+ "en": "MaxMind DB No IPv4 Search Tree",
146
+ },
147
+ DisableIPv4Aliasing: true,
148
+ IncludeReservedNetworks: true,
149
+ IPVersion: 6,
150
+ Languages: []string{"en"},
151
+ RecordSize: 24,
152
+ },
153
+ )
154
+ if err != nil {
155
+ return fmt.Errorf("creating mmdbwriter: %w", err)
156
+ }
157
+
158
+ addr, err := netip.ParsePrefix("::/64")
159
+ if err != nil {
160
+ return fmt.Errorf("parsing ip: %w", err)
161
+ }
162
+
163
+ err = dbWriter.Insert(
164
+ netipx.PrefixIPNet(addr),
165
+ mmdbtype.String(addr.String()),
166
+ )
167
+ if err != nil {
168
+ return fmt.Errorf("inserting ip: %w", err)
169
+ }
170
+
171
+ if err := w.write(dbWriter, "MaxMind-DB-no-ipv4-search-tree.mmdb"); err != nil {
172
+ return fmt.Errorf("writing database: %w", err)
173
+ }
174
+ return nil
175
+ }
176
+
177
+ // WriteNoMapTestDB writes an mmdb file where each record points to
178
+ // a string value.
179
+ func (w *Writer) WriteNoMapTestDB() error {
180
+ dbWriter, err := mmdbwriter.New(
181
+ mmdbwriter.Options{
182
+ DatabaseType: "MaxMind DB String Value Entries",
183
+ Description: map[string]string{
184
+ "en": "MaxMind DB String Value Entries (no maps or arrays as values)",
185
+ },
186
+ IPVersion: 4,
187
+ Languages: []string{"en"},
188
+ RecordSize: 24,
189
+ },
190
+ )
191
+ if err != nil {
192
+ return fmt.Errorf("creating mmdbwriter: %w", err)
193
+ }
194
+
195
+ ipRange, err := parseIPRange("1.1.1.1", "1.1.1.32")
196
+ if err != nil {
197
+ return fmt.Errorf("parsing ip range: %w", err)
198
+ }
199
+
200
+ for _, prefix := range ipRange.Prefixes() {
201
+ err := dbWriter.Insert(
202
+ netipx.PrefixIPNet(prefix),
203
+ mmdbtype.String(prefix.String()),
204
+ )
205
+ if err != nil {
206
+ return fmt.Errorf("inserting ip: %w", err)
207
+ }
208
+ }
209
+
210
+ if err := w.write(dbWriter, "MaxMind-DB-string-value-entries.mmdb"); err != nil {
211
+ return fmt.Errorf("writing database: %w", err)
212
+ }
213
+ return nil
214
+ }
215
+
216
+ // WriteMetadataPointersTestDB writes an mmdb file with metadata pointers allowed.
217
+ func (w *Writer) WriteMetadataPointersTestDB() error {
218
+ repeatedString := "Lots of pointers in metadata"
219
+ dbWriter, err := mmdbwriter.New(
220
+ mmdbwriter.Options{
221
+ DatabaseType: repeatedString,
222
+ Description: map[string]string{
223
+ "en": repeatedString,
224
+ "es": repeatedString,
225
+ "zh": repeatedString,
226
+ },
227
+ DisableIPv4Aliasing: true,
228
+ IPVersion: 6,
229
+ Languages: []string{"en", "es", "zh"},
230
+ RecordSize: 24,
231
+ },
232
+ )
233
+ if err != nil {
234
+ return fmt.Errorf("creating mmdbwriter: %w", err)
235
+ }
236
+
237
+ if err := populateAllNetworks(dbWriter); err != nil {
238
+ return fmt.Errorf("inserting all networks: %w", err)
239
+ }
240
+
241
+ if err := w.write(dbWriter, "MaxMind-DB-test-metadata-pointers.mmdb"); err != nil {
242
+ return fmt.Errorf("writing database: %w", err)
243
+ }
244
+ return nil
245
+ }
@@ -0,0 +1,73 @@
1
+ package writer
2
+
3
+ import (
4
+ "fmt"
5
+ "net/netip"
6
+
7
+ "github.com/maxmind/mmdbwriter"
8
+ "github.com/maxmind/mmdbwriter/mmdbtype"
9
+ "go4.org/netipx"
10
+ )
11
+
12
+ // WriteDeeplyNestedStructuresTestDB writes an mmdb file with deeply nested record value types.
13
+ func (w *Writer) WriteDeeplyNestedStructuresTestDB() error {
14
+ dbWriter, err := mmdbwriter.New(
15
+ mmdbwriter.Options{
16
+ DatabaseType: "MaxMind DB Nested Data Structures",
17
+ Description: map[string]string{
18
+ "en": "MaxMind DB Nested Data Structures Test database - contains deeply nested map/array structures",
19
+ },
20
+ DisableIPv4Aliasing: false,
21
+ IncludeReservedNetworks: true,
22
+ IPVersion: 6,
23
+ Languages: []string{"en"},
24
+ RecordSize: 24,
25
+ },
26
+ )
27
+ if err != nil {
28
+ return fmt.Errorf("creating mmdbwriter: %w", err)
29
+ }
30
+
31
+ addrs, err := parseIPSlice(ipSample)
32
+ if err != nil {
33
+ return fmt.Errorf("parsing ip addresses: %w", err)
34
+ }
35
+ if err := insertNestedStructure(dbWriter, addrs); err != nil {
36
+ return fmt.Errorf("inserting all types records: %w", err)
37
+ }
38
+
39
+ if err := w.write(dbWriter, "MaxMind-DB-test-nested.mmdb"); err != nil {
40
+ return fmt.Errorf("writing database: %w", err)
41
+ }
42
+ return nil
43
+ }
44
+
45
+ // insertNestedStructure inserts records with deeply nested structures.
46
+ func insertNestedStructure(w *mmdbwriter.Tree, ipAddresses []netip.Prefix) error {
47
+ nestedStruct := mmdbtype.Map{
48
+ "map1": mmdbtype.Map{
49
+ "map2": mmdbtype.Map{
50
+ "array": mmdbtype.Slice{
51
+ mmdbtype.Map{
52
+ "map3": mmdbtype.Map{
53
+ "a": mmdbtype.Uint32(1),
54
+ "b": mmdbtype.Uint32(2),
55
+ "c": mmdbtype.Uint32(3),
56
+ },
57
+ },
58
+ },
59
+ },
60
+ },
61
+ }
62
+
63
+ for _, addr := range ipAddresses {
64
+ err := w.Insert(
65
+ netipx.PrefixIPNet(addr),
66
+ nestedStruct,
67
+ )
68
+ if err != nil {
69
+ return fmt.Errorf("inserting ip: %w", err)
70
+ }
71
+ }
72
+ return nil
73
+ }
@@ -0,0 +1,58 @@
1
+ // Package writer defines database writers responsible
2
+ // for generating test mmdb files.
3
+ package writer
4
+
5
+ import (
6
+ "fmt"
7
+ "os"
8
+ "path/filepath"
9
+
10
+ "github.com/maxmind/mmdbwriter"
11
+ )
12
+
13
+ var ipSample = []string{
14
+ "::1.1.1.0/120",
15
+ "::2.2.0.0/112",
16
+ "::3.0.0.0/104",
17
+ "::4.5.6.7/128",
18
+ "abcd::/64",
19
+ "1000::1234:0000/112",
20
+ }
21
+
22
+ // Writer is responsible for writing test mmdb databases
23
+ // based on the provided data sources.
24
+ type Writer struct {
25
+ source string
26
+ target string
27
+ }
28
+
29
+ // New initializes a new test database writer struct.
30
+ func New(source, target string) (*Writer, error) {
31
+ s := filepath.Clean(source)
32
+ if _, err := os.Stat(s); os.IsNotExist(err) {
33
+ return nil, fmt.Errorf("source directory does not exist: %w", err)
34
+ }
35
+
36
+ t := filepath.Clean(target)
37
+ if err := os.MkdirAll(t, os.ModePerm); err != nil {
38
+ return nil, fmt.Errorf("creating target directory: %w", err)
39
+ }
40
+
41
+ return &Writer{
42
+ source: s,
43
+ target: t,
44
+ }, nil
45
+ }
46
+
47
+ func (w *Writer) write(dbWriter *mmdbwriter.Tree, fileName string) error {
48
+ outputFile, err := os.Create(filepath.Clean(filepath.Join(w.target, fileName)))
49
+ if err != nil {
50
+ return fmt.Errorf("creating mmdb file: %w", err)
51
+ }
52
+ defer outputFile.Close()
53
+
54
+ if _, err := dbWriter.WriteTo(outputFile); err != nil {
55
+ return fmt.Errorf("writing mmdb file: %w", err)
56
+ }
57
+ return nil
58
+ }