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,68 @@
1
+ // write-test-data generates test mmdb files.
2
+ package main
3
+
4
+ import (
5
+ "flag"
6
+ "fmt"
7
+ "os"
8
+
9
+ "github.com/maxmind/MaxMind-DB/pkg/writer"
10
+ )
11
+
12
+ func main() {
13
+ source := flag.String("source", "", "Source data directory")
14
+ target := flag.String("target", "", "Destination directory for the generated mmdb files")
15
+
16
+ flag.Parse()
17
+
18
+ w, err := writer.New(*source, *target)
19
+ if err != nil {
20
+ fmt.Printf("creating writer: %+v\n", err)
21
+ os.Exit(1)
22
+ }
23
+
24
+ if err := w.WriteIPv4TestDB(); err != nil {
25
+ fmt.Printf("writing IPv4 test databases: %+v\n", err)
26
+ os.Exit(1)
27
+ }
28
+
29
+ if err := w.WriteIPv6TestDB(); err != nil {
30
+ fmt.Printf("writing IPv6 test databases: %+v\n", err)
31
+ os.Exit(1)
32
+ }
33
+
34
+ if err := w.WriteMixedIPTestDB(); err != nil {
35
+ fmt.Printf("writing IPv6 test databases: %+v\n", err)
36
+ os.Exit(1)
37
+ }
38
+
39
+ if err := w.WriteNoIPv4TestDB(); err != nil {
40
+ fmt.Printf("writing no IPv4 test databases: %+v\n", err)
41
+ os.Exit(1)
42
+ }
43
+
44
+ if err := w.WriteNoMapTestDB(); err != nil {
45
+ fmt.Printf("writing no map test databases: %+v\n", err)
46
+ os.Exit(1)
47
+ }
48
+
49
+ if err := w.WriteMetadataPointersTestDB(); err != nil {
50
+ fmt.Printf("writing metadata pointers test databases: %+v\n", err)
51
+ os.Exit(1)
52
+ }
53
+
54
+ if err := w.WriteDecoderTestDB(); err != nil {
55
+ fmt.Printf("writing decoder test databases: %+v\n", err)
56
+ os.Exit(1)
57
+ }
58
+
59
+ if err := w.WriteDeeplyNestedStructuresTestDB(); err != nil {
60
+ fmt.Printf("writing decoder test databases: %+v\n", err)
61
+ os.Exit(1)
62
+ }
63
+
64
+ if err := w.WriteGeoIP2TestDB(); err != nil {
65
+ fmt.Printf("writing GeoIP2 test databases: %+v\n", err)
66
+ os.Exit(1)
67
+ }
68
+ }
data/test/data/go.mod ADDED
@@ -0,0 +1,13 @@
1
+ module github.com/maxmind/MaxMind-DB
2
+
3
+ go 1.21
4
+
5
+ require (
6
+ github.com/maxmind/mmdbwriter v1.0.0
7
+ go4.org/netipx v0.0.0-20230824141953-6213f710f925
8
+ )
9
+
10
+ require (
11
+ github.com/oschwald/maxminddb-golang v1.12.0 // indirect
12
+ golang.org/x/sys v0.10.0 // indirect
13
+ )
data/test/data/go.sum ADDED
@@ -0,0 +1,16 @@
1
+ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2
+ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3
+ github.com/maxmind/mmdbwriter v1.0.0 h1:bieL4P6yaYaHvbtLSwnKtEvScUKKD6jcKaLiTM3WSMw=
4
+ github.com/maxmind/mmdbwriter v1.0.0/go.mod h1:noBMCUtyN5PUQ4H8ikkOvGSHhzhLok51fON2hcrpKj8=
5
+ github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
6
+ github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
7
+ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
8
+ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
9
+ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
10
+ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
11
+ go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ=
12
+ go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
13
+ golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
14
+ golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
15
+ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
16
+ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
data/test/data/perltidyrc CHANGED
@@ -1,6 +1,7 @@
1
1
  --blank-lines-before-packages=0
2
2
  --iterations=2
3
3
  --no-outdent-long-comments
4
+ --weld-nested-containers
4
5
  -b
5
6
  -bar
6
7
  -boc
@@ -10,3 +11,8 @@
10
11
  -nolq
11
12
  -se
12
13
  -wbb="% + - * / x != == >= <= =~ !~ < > | & >= < = **= += *= &= <<= &&= -= /= |= >>= ||= .= %= ^= x="
14
+ --character-encoding=utf8
15
+ --valign-exclusion-list="q"
16
+ --want-trailing-commas=m
17
+ --add-trailing-commas
18
+ --delete-repeated-commas
@@ -0,0 +1,178 @@
1
+ package writer
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/binary"
6
+ "fmt"
7
+ "math"
8
+ "math/big"
9
+ "net/netip"
10
+
11
+ "github.com/maxmind/mmdbwriter"
12
+ "github.com/maxmind/mmdbwriter/mmdbtype"
13
+ "go4.org/netipx"
14
+ )
15
+
16
+ // WriteDecoderTestDB writes an mmdb file with all possible record value types.
17
+ func (w *Writer) WriteDecoderTestDB() error {
18
+ dbWriter, err := mmdbwriter.New(
19
+ mmdbwriter.Options{
20
+ DatabaseType: "MaxMind DB Decoder Test",
21
+ Description: map[string]string{
22
+ "en": "MaxMind DB Decoder Test database - contains every MaxMind DB data type",
23
+ },
24
+ DisableIPv4Aliasing: false,
25
+ IncludeReservedNetworks: true,
26
+ IPVersion: 6,
27
+ Languages: []string{"en"},
28
+ RecordSize: 24,
29
+ },
30
+ )
31
+ if err != nil {
32
+ return fmt.Errorf("creating mmdbwriter: %w", err)
33
+ }
34
+
35
+ addrs, err := parseIPSlice(ipSample)
36
+ if err != nil {
37
+ return fmt.Errorf("parsing ip addresses: %w", err)
38
+ }
39
+ if err := insertAllTypes(dbWriter, addrs); err != nil {
40
+ return fmt.Errorf("inserting all types records: %w", err)
41
+ }
42
+
43
+ zeroAddr, err := netip.ParsePrefix("::0.0.0.0/128")
44
+ if err != nil {
45
+ return fmt.Errorf("parsing ip: %w", err)
46
+ }
47
+ if err := insertAllTypesZero(dbWriter, []netip.Prefix{zeroAddr}); err != nil {
48
+ return fmt.Errorf("inserting all types records: %w", err)
49
+ }
50
+
51
+ maxAddr, err := netip.ParsePrefix("::255.255.255.255/128")
52
+ if err != nil {
53
+ return fmt.Errorf("parsing ip: %w", err)
54
+ }
55
+ if err := insertNumericMax(dbWriter, []netip.Prefix{maxAddr}); err != nil {
56
+ return fmt.Errorf("inserting all types records: %w", err)
57
+ }
58
+
59
+ if err := w.write(dbWriter, "MaxMind-DB-test-decoder.mmdb"); err != nil {
60
+ return fmt.Errorf("writing database: %w", err)
61
+ }
62
+ return nil
63
+ }
64
+
65
+ // insertAllTypes inserts records with all possible value types.
66
+ func insertAllTypes(w *mmdbwriter.Tree, ipAddresses []netip.Prefix) error {
67
+ buf := new(bytes.Buffer)
68
+ if err := binary.Write(buf, binary.BigEndian, uint32(42)); err != nil {
69
+ return fmt.Errorf("creating buffer for all types record: %w", err)
70
+ }
71
+
72
+ ui64 := big.Int{}
73
+ ui64.Lsh(big.NewInt(1), 60)
74
+
75
+ ui128 := big.Int{}
76
+ ui128.Lsh(big.NewInt(1), 120)
77
+ mmdbUint128 := mmdbtype.Uint128(ui128)
78
+
79
+ allTypes := mmdbtype.Map{
80
+ "array": mmdbtype.Slice{
81
+ mmdbtype.Uint32(1),
82
+ mmdbtype.Uint32(2),
83
+ mmdbtype.Uint32(3),
84
+ },
85
+ "bytes": mmdbtype.Bytes(buf.Bytes()),
86
+ "boolean": mmdbtype.Bool(true),
87
+ "double": mmdbtype.Float64(42.123456),
88
+ "float": mmdbtype.Float32(1.1),
89
+ "int32": mmdbtype.Int32(-1 * math.Pow(2, 28)),
90
+ "map": mmdbtype.Map{
91
+ "mapX": mmdbtype.Map{
92
+ "utf8_stringX": mmdbtype.String("hello"),
93
+ "arrayX": mmdbtype.Slice{
94
+ mmdbtype.Uint32(7),
95
+ mmdbtype.Uint32(8),
96
+ mmdbtype.Uint32(9),
97
+ },
98
+ },
99
+ },
100
+ "uint16": mmdbtype.Uint16(100),
101
+ "uint32": mmdbtype.Uint32(math.Pow(2, 28)),
102
+ "uint64": mmdbtype.Uint64(ui64.Uint64()),
103
+ "uint128": mmdbUint128.Copy(),
104
+ "utf8_string": mmdbtype.String("unicode! ☯ - ♫"),
105
+ }
106
+
107
+ for _, addr := range ipAddresses {
108
+ err := w.Insert(
109
+ netipx.PrefixIPNet(addr),
110
+ allTypes,
111
+ )
112
+ if err != nil {
113
+ return fmt.Errorf("inserting ip: %w", err)
114
+ }
115
+ }
116
+ return nil
117
+ }
118
+
119
+ // insertAllTypesZero inserts records with all possible value types with zero values.
120
+ func insertAllTypesZero(w *mmdbwriter.Tree, ipAddresses []netip.Prefix) error {
121
+ var uint128 big.Int
122
+ mmdbUint128 := mmdbtype.Uint128(uint128)
123
+
124
+ zeroValues := mmdbtype.Map{
125
+ "array": mmdbtype.Slice{},
126
+ "bytes": mmdbtype.Bytes([]byte{}),
127
+ "boolean": mmdbtype.Bool(false),
128
+ "double": mmdbtype.Float64(0),
129
+ "float": mmdbtype.Float32(0),
130
+ "int32": mmdbtype.Int32(0),
131
+ "map": mmdbtype.Map{},
132
+ "uint16": mmdbtype.Uint16(0),
133
+ "uint32": mmdbtype.Uint32(0),
134
+ "uint64": mmdbtype.Uint64(0),
135
+ "uint128": mmdbUint128.Copy(),
136
+ "utf8_string": mmdbtype.String(""),
137
+ }
138
+
139
+ for _, addr := range ipAddresses {
140
+ err := w.Insert(
141
+ netipx.PrefixIPNet(addr),
142
+ zeroValues,
143
+ )
144
+ if err != nil {
145
+ return fmt.Errorf("inserting ip: %w", err)
146
+ }
147
+ }
148
+ return nil
149
+ }
150
+
151
+ // insertNumericMax inserts records with numeric types maxed out.
152
+ func insertNumericMax(w *mmdbwriter.Tree, ipAddresses []netip.Prefix) error {
153
+ var uint128Max big.Int
154
+ uint128Max.Exp(big.NewInt(2), big.NewInt(128), nil)
155
+ uint128Max.Sub(&uint128Max, big.NewInt(1))
156
+ mmdbUint128 := mmdbtype.Uint128(uint128Max)
157
+
158
+ numMax := mmdbtype.Map{
159
+ "double": mmdbtype.Float64(math.Inf(1)),
160
+ "float": mmdbtype.Float32(float32(math.Inf(1))),
161
+ "int32": mmdbtype.Int32(1<<31 - 1),
162
+ "uint16": mmdbtype.Uint16(0xffff),
163
+ "uint32": mmdbtype.Uint32(0xffffffff),
164
+ "uint64": mmdbtype.Uint64(0xffffffffffffffff),
165
+ "uint128": mmdbUint128.Copy(),
166
+ }
167
+
168
+ for _, addr := range ipAddresses {
169
+ err := w.Insert(
170
+ netipx.PrefixIPNet(addr),
171
+ numMax,
172
+ )
173
+ if err != nil {
174
+ return fmt.Errorf("inserting ip: %w", err)
175
+ }
176
+ }
177
+ return nil
178
+ }
@@ -0,0 +1,182 @@
1
+ package writer
2
+
3
+ import (
4
+ "encoding/json"
5
+ "fmt"
6
+ "net/netip"
7
+ "os"
8
+ "path/filepath"
9
+ "strings"
10
+
11
+ "github.com/maxmind/mmdbwriter"
12
+ "github.com/maxmind/mmdbwriter/mmdbtype"
13
+ "go4.org/netipx"
14
+ )
15
+
16
+ // WriteGeoIP2TestDB writes GeoIP2 test mmdb files.
17
+ func (w *Writer) WriteGeoIP2TestDB() error {
18
+ dbTypes := []string{
19
+ "GeoIP2-Anonymous-IP",
20
+ "GeoIP2-City",
21
+ "GeoIP2-Connection-Type",
22
+ "GeoIP2-Country",
23
+ "GeoIP2-DensityIncome",
24
+ "GeoIP2-Domain",
25
+ "GeoIP2-Enterprise",
26
+ "GeoIP2-ISP",
27
+ "GeoIP2-Precision-Enterprise",
28
+ "GeoIP2-Static-IP-Score",
29
+ "GeoIP2-User-Count",
30
+ "GeoLite2-ASN",
31
+ "GeoLite2-City",
32
+ "GeoLite2-Country",
33
+ }
34
+
35
+ for _, dbType := range dbTypes {
36
+ languages := []string{"en"}
37
+ description := map[string]string{
38
+ "en": strings.ReplaceAll(dbType, "-", " ") +
39
+ " Test Database (fake GeoIP2 data, for example purposes only)",
40
+ }
41
+
42
+ if dbType == "GeoIP2-City" {
43
+ languages = append(languages, "zh")
44
+ description["zh"] = "小型数据库"
45
+ }
46
+
47
+ dbWriter, err := mmdbwriter.New(
48
+ mmdbwriter.Options{
49
+ DatabaseType: dbType,
50
+ Description: description,
51
+ DisableIPv4Aliasing: false,
52
+ IPVersion: 6,
53
+ Languages: languages,
54
+ RecordSize: 28,
55
+ },
56
+ )
57
+ if err != nil {
58
+ return fmt.Errorf("creating mmdbwriter: %w", err)
59
+ }
60
+
61
+ if dbType == "GeoIP2-Anonymous-IP" {
62
+ if err := populateAllNetworks(dbWriter); err != nil {
63
+ return fmt.Errorf("inserting all networks: %w", err)
64
+ }
65
+ }
66
+
67
+ jsonFileName := fmt.Sprintf("%s-Test.json", dbType)
68
+ if err := w.insertJSON(dbWriter, jsonFileName); err != nil {
69
+ return fmt.Errorf("inserting json: %w", err)
70
+ }
71
+
72
+ dbFileName := fmt.Sprintf("%s-Test.mmdb", dbType)
73
+ if err := w.write(dbWriter, dbFileName); err != nil {
74
+ return fmt.Errorf("writing database: %w", err)
75
+ }
76
+ }
77
+
78
+ return nil
79
+ }
80
+
81
+ // insertJSON reads and parses a json file into mmdbtypes values and inserts
82
+ // them into the mmdbwriter tree.
83
+ func (w *Writer) insertJSON(dbWriter *mmdbwriter.Tree, fileName string) error {
84
+ file, err := os.Open(filepath.Clean(filepath.Join(w.source, fileName)))
85
+ if err != nil {
86
+ return fmt.Errorf("opening json file: %w", err)
87
+ }
88
+ defer file.Close()
89
+
90
+ var data []map[string]any
91
+ if err := json.NewDecoder(file).Decode(&data); err != nil {
92
+ return fmt.Errorf("decoding json file: %w", err)
93
+ }
94
+
95
+ for _, record := range data {
96
+ for k, v := range record {
97
+ prefix, err := netip.ParsePrefix(k)
98
+ if err != nil {
99
+ return fmt.Errorf("parsing ip: %w", err)
100
+ }
101
+
102
+ mmdbValue, err := toMMDBType(prefix.String(), v)
103
+ if err != nil {
104
+ return fmt.Errorf("converting value to mmdbtype: %w", err)
105
+ }
106
+
107
+ err = dbWriter.Insert(
108
+ netipx.PrefixIPNet(prefix),
109
+ mmdbValue,
110
+ )
111
+ if err != nil {
112
+ return fmt.Errorf("inserting ip: %w", err)
113
+ }
114
+ }
115
+ }
116
+ return nil
117
+ }
118
+
119
+ // toMMDBType key converts field values read from json into their corresponding mmdbtype.DataType.
120
+ // It makes some assumptions for numeric types based on previous knowledge about field types.
121
+ func toMMDBType(key string, value any) (mmdbtype.DataType, error) {
122
+ switch v := value.(type) {
123
+ case bool:
124
+ return mmdbtype.Bool(v), nil
125
+ case string:
126
+ return mmdbtype.String(v), nil
127
+ case map[string]any:
128
+ m := mmdbtype.Map{}
129
+ for innerKey, val := range v {
130
+ innerVal, err := toMMDBType(innerKey, val)
131
+ if err != nil {
132
+ return nil, fmt.Errorf("parsing mmdbtype.Map for key %q: %w", key, err)
133
+ }
134
+ m[mmdbtype.String(innerKey)] = innerVal
135
+ }
136
+ return m, nil
137
+ case []any:
138
+ s := mmdbtype.Slice{}
139
+ for _, val := range v {
140
+ innerVal, err := toMMDBType(key, val)
141
+ if err != nil {
142
+ return nil, fmt.Errorf("parsing mmdbtype.Slice for key %q: %w", key, err)
143
+ }
144
+ s = append(s, innerVal)
145
+ }
146
+ return s, nil
147
+ case float64:
148
+ switch key {
149
+ case "accuracy_radius", "confidence", "metro_code":
150
+ return mmdbtype.Uint16(v), nil
151
+ case "autonomous_system_number", "average_income",
152
+ "geoname_id", "ipv4_24", "ipv4_32", "ipv6_32",
153
+ "ipv6_48", "ipv6_64", "population_density":
154
+ return mmdbtype.Uint32(v), nil
155
+ case "ip_risk", "latitude", "longitude", "score",
156
+ "static_ip_score":
157
+ return mmdbtype.Float64(v), nil
158
+ default:
159
+ return nil, fmt.Errorf("unsupported numberic type for key %q: %T", key, value)
160
+ }
161
+ default:
162
+ return nil, fmt.Errorf("unsupported type for key %q: %T", key, value)
163
+ }
164
+ }
165
+
166
+ // populate all networks inserts all networks into the writer with an empty map value.
167
+ func populateAllNetworks(w *mmdbwriter.Tree) error {
168
+ defaultNet, err := netip.ParsePrefix("::/0")
169
+ if err != nil {
170
+ return fmt.Errorf("parsing ip: %w", err)
171
+ }
172
+
173
+ err = w.Insert(
174
+ netipx.PrefixIPNet(defaultNet),
175
+ mmdbtype.Map{},
176
+ )
177
+ if err != nil {
178
+ return fmt.Errorf("inserting ip: %w", err)
179
+ }
180
+
181
+ return nil
182
+ }
@@ -0,0 +1,39 @@
1
+ package writer
2
+
3
+ import (
4
+ "fmt"
5
+ "net/netip"
6
+
7
+ "go4.org/netipx"
8
+ )
9
+
10
+ // parseIPRange takes IP addresses in string presentation form that represent a
11
+ // range and returns an IP range.
12
+ func parseIPRange(from, to string) (netipx.IPRange, error) {
13
+ startIP, err := netip.ParseAddr(from)
14
+ if err != nil {
15
+ return netipx.IPRange{}, fmt.Errorf("parsing %s as an IP: %w", from, err)
16
+ }
17
+ endIP, err := netip.ParseAddr(to)
18
+ if err != nil {
19
+ return netipx.IPRange{}, fmt.Errorf("parsing %s as an IP: %w", to, err)
20
+ }
21
+ ipRange := netipx.IPRangeFrom(startIP, endIP)
22
+ if !ipRange.IsValid() {
23
+ return netipx.IPRange{}, fmt.Errorf("%s-%s is an invalid IP range", startIP, endIP)
24
+ }
25
+ return ipRange, nil
26
+ }
27
+
28
+ // parseIPSlice parses a slice of IP address strings and returns a slice of netip.Prefix.
29
+ func parseIPSlice(ipAddresses []string) ([]netip.Prefix, error) {
30
+ var addrs []netip.Prefix
31
+ for _, ip := range ipAddresses {
32
+ addr, err := netip.ParsePrefix(ip)
33
+ if err != nil {
34
+ return nil, fmt.Errorf("parsing %s as an IP: %w", ip, err)
35
+ }
36
+ addrs = append(addrs, addr)
37
+ }
38
+ return addrs, nil
39
+ }