maxmind-geoip2 1.0.0 → 1.2.0

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 +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
+ }