diff --git a/go.mod b/go.mod index b7ceadbdc..716d49a33 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/go-kit/kit v0.12.0 github.com/golang/snappy v0.0.4 github.com/heptiolabs/healthcheck v0.0.0-20211123025425-613501dd5deb - github.com/ip2location/ip2location-go/v9 v9.2.0 + github.com/ip2location/ip2location-go/v9 v9.6.0 github.com/json-iterator/go v1.1.12 github.com/mariomac/guara v0.0.0-20220523124851-5fc279816f1f github.com/minio/minio-go/v7 v7.0.63 @@ -109,6 +109,7 @@ require ( k8s.io/klog/v2 v2.100.1 // indirect k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect + lukechampine.com/uint128 v1.2.0 // indirect sigs.k8s.io/controller-runtime v0.15.1 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect diff --git a/go.sum b/go.sum index 1890081d8..46b552198 100644 --- a/go.sum +++ b/go.sum @@ -502,8 +502,8 @@ github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19y github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= -github.com/ip2location/ip2location-go/v9 v9.2.0 h1:ZWURUspTpe2gxMQGtcQScocBd73gZKUEWlHCFUJYLHk= -github.com/ip2location/ip2location-go/v9 v9.2.0/go.mod h1:s5SV6YZL10TpfPpXw//7fEJC65G/yH7Oh+Tjq9JcQEQ= +github.com/ip2location/ip2location-go/v9 v9.6.0 h1:do+hKM2wbVG3lXMavZzWuy0znXxJBvGc7mv0wzRVoYc= +github.com/ip2location/ip2location-go/v9 v9.6.0/go.mod h1:MPLnsKxwQlvd2lBNcQCsLoyzJLDBFizuO67wXXdzoyI= github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -1473,6 +1473,8 @@ k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/ k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= diff --git a/vendor/github.com/ip2location/ip2location-go/v9/LICENSE.TXT b/vendor/github.com/ip2location/ip2location-go/v9/LICENSE.TXT index a54dedbf1..3f56aae67 100644 --- a/vendor/github.com/ip2location/ip2location-go/v9/LICENSE.TXT +++ b/vendor/github.com/ip2location/ip2location-go/v9/LICENSE.TXT @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 IP2Location.com +Copyright (c) 2023 IP2Location.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/github.com/ip2location/ip2location-go/v9/README.md b/vendor/github.com/ip2location/ip2location-go/v9/README.md index 14a6094dc..85a173064 100644 --- a/vendor/github.com/ip2location/ip2location-go/v9/README.md +++ b/vendor/github.com/ip2location/ip2location-go/v9/README.md @@ -1,9 +1,9 @@ -[![Go Report Card](https://goreportcard.com/badge/github.com/ip2location/ip2location-go)](https://goreportcard.com/report/github.com/ip2location/ip2location-go) - +[![Go Report Card](https://goreportcard.com/badge/github.com/ip2location/ip2location-go/v9)](https://goreportcard.com/report/github.com/ip2location/ip2location-go/v9) +[![PkgGoDev](https://pkg.go.dev/badge/github.com/ip2location/ip2location-go/v9)](https://pkg.go.dev/github.com/ip2location/ip2location-go/v9) # IP2Location Go Package -This Go package provides a fast lookup of country, region, city, latitude, longitude, ZIP code, time zone, ISP, domain name, connection type, IDD code, area code, weather station code, station name, mcc, mnc, mobile brand, elevation, usage type, address type and IAB category from IP address by using IP2Location database. This package uses a file based database available at IP2Location.com. This database simply contains IP blocks as keys, and other information such as country, region, city, latitude, longitude, ZIP code, time zone, ISP, domain name, connection type, IDD code, area code, weather station code, station name, mcc, mnc, mobile brand, elevation, usage type, address type and IAB category as values. It supports both IP address in IPv4 and IPv6. +This Go package provides a fast lookup of country, region, city, latitude, longitude, ZIP code, time zone, ISP, domain name, connection type, IDD code, area code, weather station code, station name, mcc, mnc, mobile brand, elevation, usage type, address type, IAB category, district, autonomous system number (ASN) and autonomous system (AS) from IP address by using IP2Location database. This package uses a file based database available at IP2Location.com. This database simply contains IP blocks as keys, and other information such as country, region, city, latitude, longitude, ZIP code, time zone, ISP, domain name, connection type, IDD code, area code, weather station code, station name, mcc, mnc, mobile brand, elevation, usage type, address type, IAB category, district, autonomous system number (ASN) and autonomous system (AS) as values. It supports both IP address in IPv4 and IPv6. This package can be used in many types of projects such as: @@ -75,6 +75,9 @@ Below are the methods supported in this package. |Get_usagetype|Returns the usage type.| |Get_addresstype|Returns the address type.| |Get_category|Returns the IAB category.| +|Get_district|Returns the district name.| +|Get_asn|Returns the autonomous system number (ASN).| +|Get_as|Returns the autonomous system (AS).| |Close|Closes BIN file.| ## Usage @@ -84,11 +87,11 @@ package main import ( "fmt" - "github.com/ip2location/ip2location-go" + "github.com/ip2location/ip2location-go/v9" ) func main() { - db, err := ip2location.OpenDB("./IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE-ADDRESSTYPE-CATEGORY.BIN") + db, err := ip2location.OpenDB("./IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE-ADDRESSTYPE-CATEGORY-DISTRICT-ASN.BIN") if err != nil { fmt.Print(err) @@ -124,6 +127,9 @@ func main() { fmt.Printf("usagetype: %s\n", results.Usagetype) fmt.Printf("addresstype: %s\n", results.Addresstype) fmt.Printf("category: %s\n", results.Category) + fmt.Printf("district: %s\n", results.District) + fmt.Printf("asn: %s\n", results.Asn) + fmt.Printf("as: %s\n", results.As) fmt.Printf("api version: %s\n", ip2location.Api_version()) db.Close() @@ -147,7 +153,7 @@ Below are the methods supported in this package. package main import ( - "github.com/ip2location/ip2location-go" + "github.com/ip2location/ip2location-go/v9" "fmt" ) @@ -261,4 +267,256 @@ func main() { fmt.Printf("Credit Balance: %d\n", res2.Response) } +``` + +## IPTOOLS CLASS + +## Methods +Below are the methods supported in this package. + +|Method Name|Description| +|---|---| +|func (t *IPTools) IsIPv4(IP string) bool|Returns true if string contains an IPv4 address. Otherwise false.| +|func (t *IPTools) IsIPv6(IP string) bool|Returns true if string contains an IPv6 address. Otherwise false.| +|func (t *IPTools) IPv4ToDecimal(IP string) (*big.Int, error)|Returns the IP number for an IPv4 address.| +|func (t *IPTools) IPv6ToDecimal(IP string) (*big.Int, error)|Returns the IP number for an IPv6 address.| +|func (t *IPTools) DecimalToIPv4(IPNum *big.Int) (string, error)|Returns the IPv4 address for the supplied IP number.| +|func (t *IPTools) DecimalToIPv6(IPNum *big.Int) (string, error)|Returns the IPv6 address for the supplied IP number.| +|func (t *IPTools) CompressIPv6(IP string) (string, error)|Returns the IPv6 address in compressed form.| +|func (t *IPTools) ExpandIPv6(IP string) (string, error)|Returns the IPv6 address in expanded form.| +|func (t *IPTools) IPv4ToCIDR(IPFrom string, IPTo string) ([]string, error)|Returns a list of CIDR from the supplied IPv4 range.| +|func (t *IPTools) IPv6ToCIDR(IPFrom string, IPTo string) ([]string, error)|Returns a list of CIDR from the supplied IPv6 range.| +|func (t *IPTools) CIDRToIPv4(CIDR string) ([]string, error)|Returns the IPv4 range from the supplied CIDR.| +|func (t *IPTools) CIDRToIPv6(CIDR string) ([]string, error)|Returns the IPv6 range from the supplied CIDR.| + +## Usage + +```go +package main + +import ( + "github.com/ip2location/ip2location-go/v9" + "fmt" + "math/big" +) + +func main() { + t := ip2location.OpenTools() + + ip := "8.8.8.8" + res := t.IsIPv4(ip) + + fmt.Printf("Is IPv4: %t\n", res) + + ipnum, err := t.IPv4ToDecimal(ip) + if err != nil { + fmt.Print(err) + } else { + fmt.Printf("IPNum: %v\n", ipnum) + } + + ip2 := "2600:1f18:45b0:5b00:f5d8:4183:7710:ceec" + res2 := t.IsIPv6(ip2) + + fmt.Printf("Is IPv6: %t\n", res2) + + ipnum2, err := t.IPv6ToDecimal(ip2) + if err != nil { + fmt.Print(err) + } else { + fmt.Printf("IPNum: %v\n", ipnum2) + } + + ipnum3 := big.NewInt(42534) + res3, err := t.DecimalToIPv4(ipnum3) + + if err != nil { + fmt.Print(err) + } else { + fmt.Printf("IPv4: %v\n", res3) + } + + ipnum4, ok := big.NewInt(0).SetString("22398978840339333967292465152", 10) + if ok { + res4, err := t.DecimalToIPv6(ipnum4) + if err != nil { + fmt.Print(err) + } else { + fmt.Printf("IPv6: %v\n", res4) + } + } + + ip3 := "2600:1f18:045b:005b:f5d8:0:000:ceec" + res5, err := t.CompressIPv6(ip3) + + if err != nil { + fmt.Print(err) + } else { + fmt.Printf("Compressed: %v\n", res5) + } + + ip4 := "::45b:05b:f5d8:0:000:ceec" + res6, err := t.ExpandIPv6(ip4) + + if err != nil { + fmt.Print(err) + } else { + fmt.Printf("Expanded: %v\n", res6) + } + + res7, err := t.IPv4ToCIDR("10.0.0.0", "10.10.2.255") + + if err != nil { + fmt.Print(err) + } else { + for _, element := range res7 { + fmt.Println(element) + } + } + + res8, err := t.IPv6ToCIDR("2001:4860:4860:0000:0000:0000:0000:8888", "2001:4860:4860:0000:eeee:ffff:ffff:ffff") + + if err != nil { + fmt.Print(err) + } else { + for _, element := range res8 { + fmt.Println(element) + } + } + + res9, err := t.CIDRToIPv4("123.245.99.13/26") + + if err != nil { + fmt.Print(err) + } else { + fmt.Printf("IPv4 Range: %v\n", res9) + } + + res10, err := t.CIDRToIPv6("2002:1234::abcd:ffff:c0a8:101/62") + + if err != nil { + fmt.Print(err) + } else { + fmt.Printf("IPv6 Range: %v\n", res10) + } +} +``` + +## COUNTRY CLASS + +## Methods +Below are the methods supported in this package. + +|Method Name|Description| +|---|---| +|func OpenCountryInfo(csvFile string) (*CI, error)|Expect a IP2Location Country Information CSV file. This database is free for download at https://www.ip2location.com/free/country-information| +|func (c *CI) GetCountryInfo(countryCode ...string) ([]CountryInfoRecord, error)|Returns the country information for specified country or all countries.| + +## Usage + +```go +package main + +import ( + "github.com/ip2location/ip2location-go" + "fmt" +) + +func main() { + c, err := ip2location.OpenCountryInfo("./IP2LOCATION-COUNTRY-INFORMATION.CSV") + + if err != nil { + fmt.Print(err) + return + } + + res, err := c.GetCountryInfo("US") + + if err != nil { + fmt.Print(err) + return + } + + fmt.Printf("country_code: %s\n", res[0].Country_code) + fmt.Printf("country_name: %s\n", res[0].Country_name) + fmt.Printf("country_alpha3_code: %s\n", res[0].Country_alpha3_code) + fmt.Printf("country_numeric_code: %s\n", res[0].Country_numeric_code) + fmt.Printf("capital: %s\n", res[0].Capital) + fmt.Printf("country_demonym: %s\n", res[0].Country_demonym) + fmt.Printf("total_area: %s\n", res[0].Total_area) + fmt.Printf("population: %s\n", res[0].Population) + fmt.Printf("idd_code: %s\n", res[0].Idd_code) + fmt.Printf("currency_code: %s\n", res[0].Currency_code) + fmt.Printf("currency_name: %s\n", res[0].Currency_name) + fmt.Printf("currency_symbol: %s\n", res[0].Currency_symbol) + fmt.Printf("lang_code: %s\n", res[0].Lang_code) + fmt.Printf("lang_name: %s\n", res[0].Lang_name) + fmt.Printf("cctld: %s\n", res[0].Cctld) + fmt.Print("==============================================\n") + + res2, err := c.GetCountryInfo() + + if err != nil { + fmt.Print(err) + return + } + + for _, v := range res2 { + fmt.Printf("country_code: %s\n", v.Country_code) + fmt.Printf("country_name: %s\n", v.Country_name) + fmt.Printf("country_alpha3_code: %s\n", v.Country_alpha3_code) + fmt.Printf("country_numeric_code: %s\n", v.Country_numeric_code) + fmt.Printf("capital: %s\n", v.Capital) + fmt.Printf("country_demonym: %s\n", v.Country_demonym) + fmt.Printf("total_area: %s\n", v.Total_area) + fmt.Printf("population: %s\n", v.Population) + fmt.Printf("idd_code: %s\n", v.Idd_code) + fmt.Printf("currency_code: %s\n", v.Currency_code) + fmt.Printf("currency_name: %s\n", v.Currency_name) + fmt.Printf("currency_symbol: %s\n", v.Currency_symbol) + fmt.Printf("lang_code: %s\n", v.Lang_code) + fmt.Printf("lang_name: %s\n", v.Lang_name) + fmt.Printf("cctld: %s\n", v.Cctld) + fmt.Print("==============================================\n") + } +} +``` + +## REGION CLASS + +## Methods +Below are the methods supported in this package. + +|Method Name|Description| +|---|---| +|func OpenRegionInfo(csvFile string) (*RI, error)|Expect a IP2Location ISO 3166-2 Subdivision Code CSV file. This database is free for download at https://www.ip2location.com/free/iso3166-2| +|func (r *RI) GetRegionCode(countryCode string, regionName string) (string, error)|Returns the region code for the supplied country code and region name.| + +## Usage + +```go +package main + +import ( + "github.com/ip2location/ip2location-go" + "fmt" +) + +func main() { + r, err := ip2location.OpenRegionInfo("./IP2LOCATION-ISO3166-2.CSV") + + if err != nil { + fmt.Print(err) + return + } + + res, err := r.GetRegionCode("US", "California") + + if err != nil { + fmt.Print(err) + return + } + + fmt.Printf("region code: %s\n", res) +} ``` \ No newline at end of file diff --git a/vendor/github.com/ip2location/ip2location-go/v9/country.go b/vendor/github.com/ip2location/ip2location-go/v9/country.go new file mode 100644 index 000000000..202ce316c --- /dev/null +++ b/vendor/github.com/ip2location/ip2location-go/v9/country.go @@ -0,0 +1,129 @@ +package ip2location + +import ( + "encoding/csv" + "errors" + "os" +) + +// The CountryInfoRecord struct stores all of the available +// country info found in the country information CSV file. +type CountryInfoRecord struct { + Country_code string + Country_name string + Country_alpha3_code string + Country_numeric_code string + Capital string + Country_demonym string + Total_area string + Population string + Idd_code string + Currency_code string + Currency_name string + Currency_symbol string + Lang_code string + Lang_name string + Cctld string +} + +// The CI struct is the main object used to read the country information CSV. +type CI struct { + resultsArr []CountryInfoRecord + resultsMap map[string]CountryInfoRecord +} + +// OpenCountryInfo initializes with the path to the country information CSV file. +func OpenCountryInfo(csvFile string) (*CI, error) { + var ci = &CI{} + + _, err := os.Stat(csvFile) + if os.IsNotExist(err) { + return nil, errors.New("The CSV file '" + csvFile + "' is not found.") + } + + f, err := os.Open(csvFile) + if err != nil { + return nil, errors.New("Unable to read '" + csvFile + "'.") + } + + defer f.Close() + + csvReader := csv.NewReader(f) + data, err := csvReader.ReadAll() + if err != nil { + return nil, errors.New("Unable to read '" + csvFile + "'.") + } + + ci.resultsMap = make(map[string]CountryInfoRecord) + var headerArr []string + + for i, line := range data { + if i == 0 { // headers + for _, field := range line { + headerArr = append(headerArr, field) + } + } else { + var rec CountryInfoRecord + for j, field := range line { + switch headerArr[j] { + case "country_code": + rec.Country_code = field + case "country_name": + rec.Country_name = field + case "country_alpha3_code": + rec.Country_alpha3_code = field + case "country_numeric_code": + rec.Country_numeric_code = field + case "capital": + rec.Capital = field + case "country_demonym": + rec.Country_demonym = field + case "total_area": + rec.Total_area = field + case "population": + rec.Population = field + case "idd_code": + rec.Idd_code = field + case "currency_code": + rec.Currency_code = field + case "currency_name": + rec.Currency_name = field + case "currency_symbol": + rec.Currency_symbol = field + case "lang_code": + rec.Lang_code = field + case "lang_name": + rec.Lang_name = field + case "cctld": + rec.Cctld = field + } + } + if rec.Country_code == "" { + return nil, errors.New("Invalid country information CSV file.") + } + ci.resultsArr = append(ci.resultsArr, rec) + ci.resultsMap[rec.Country_code] = rec + } + } + return ci, nil +} + +// GetCountryInfo returns the country information for the specified country or all countries if not specified +func (c *CI) GetCountryInfo(countryCode ...string) ([]CountryInfoRecord, error) { + if len(c.resultsArr) == 0 { + return nil, errors.New("No record available.") + } + + if len(countryCode) == 1 { + cc := countryCode[0] + if rec, ok := c.resultsMap[cc]; ok { + var x []CountryInfoRecord + x = append(x, rec) + return x, nil // return record + } else { + return nil, errors.New("No record found.") + } + } else { + return c.resultsArr, nil // return all countries + } +} diff --git a/vendor/github.com/ip2location/ip2location-go/v9/ip2location.go b/vendor/github.com/ip2location/ip2location-go/v9/ip2location.go index 319cb651f..4f4a6d53e 100644 --- a/vendor/github.com/ip2location/ip2location-go/v9/ip2location.go +++ b/vendor/github.com/ip2location/ip2location-go/v9/ip2location.go @@ -1,6 +1,7 @@ // This ip2location package provides a fast lookup of country, region, city, latitude, longitude, ZIP code, time zone, // ISP, domain name, connection type, IDD code, area code, weather station code, station name, MCC, MNC, -// mobile brand, elevation, usage type, address type and IAB category from IP address by using IP2Location database. +// mobile brand, elevation, usage type, address type, IAB category, district, autonomous system number (ASN) and +// autonomous system (AS) from IP address by using IP2Location database. package ip2location import ( @@ -9,11 +10,13 @@ import ( "errors" "fmt" "io" + "lukechampine.com/uint128" "math" "math/big" "net" "os" "strconv" + "unsafe" ) type DBReader interface { @@ -31,6 +34,8 @@ type ip2locationmeta struct { ipv4databaseaddr uint32 ipv6databasecount uint32 ipv6databaseaddr uint32 + ipv4indexed bool + ipv6indexed bool ipv4indexbaseaddr uint32 ipv6indexbaseaddr uint32 ipv4columnsize uint32 @@ -65,6 +70,9 @@ type IP2Locationrecord struct { Usagetype string Addresstype string Category string + District string + Asn string + As string } type DB struct { @@ -92,6 +100,9 @@ type DB struct { usagetype_position_offset uint32 addresstype_position_offset uint32 category_position_offset uint32 + district_position_offset uint32 + asn_position_offset uint32 + as_position_offset uint32 country_enabled bool region_enabled bool @@ -114,81 +125,96 @@ type DB struct { usagetype_enabled bool addresstype_enabled bool category_enabled bool + district_enabled bool + asn_enabled bool + as_enabled bool metaok bool } var defaultDB = &DB{} -var country_position = [26]uint8{0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2} -var region_position = [26]uint8{0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3} -var city_position = [26]uint8{0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4} -var isp_position = [26]uint8{0, 0, 3, 0, 5, 0, 7, 5, 7, 0, 8, 0, 9, 0, 9, 0, 9, 0, 9, 7, 9, 0, 9, 7, 9, 9} -var latitude_position = [26]uint8{0, 0, 0, 0, 0, 5, 5, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5} -var longitude_position = [26]uint8{0, 0, 0, 0, 0, 6, 6, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6} -var domain_position = [26]uint8{0, 0, 0, 0, 0, 0, 0, 6, 8, 0, 9, 0, 10, 0, 10, 0, 10, 0, 10, 8, 10, 0, 10, 8, 10, 10} -var zipcode_position = [26]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, 7, 0, 7, 7, 7, 0, 7, 0, 7, 7, 7, 0, 7, 7} -var timezone_position = [26]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 7, 8, 8, 8, 7, 8, 0, 8, 8, 8, 0, 8, 8} -var netspeed_position = [26]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 11, 0, 11, 8, 11, 0, 11, 0, 11, 0, 11, 11} -var iddcode_position = [26]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 12, 0, 12, 0, 12, 9, 12, 0, 12, 12} -var areacode_position = [26]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 13, 0, 13, 0, 13, 10, 13, 0, 13, 13} -var weatherstationcode_position = [26]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 14, 0, 14, 0, 14, 0, 14, 14} -var weatherstationname_position = [26]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 15, 0, 15, 0, 15, 0, 15, 15} -var mcc_position = [26]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 16, 0, 16, 9, 16, 16} -var mnc_position = [26]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 17, 0, 17, 10, 17, 17} -var mobilebrand_position = [26]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 18, 0, 18, 11, 18, 18} -var elevation_position = [26]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 19, 0, 19, 19} -var usagetype_position = [26]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 20, 20} -var addresstype_position = [26]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21} -var category_position = [26]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22} - -const api_version string = "9.2.0" - -var max_ipv4_range = big.NewInt(4294967295) -var max_ipv6_range = big.NewInt(0) -var from_v4mapped = big.NewInt(281470681743360) -var to_v4mapped = big.NewInt(281474976710655) -var from_6to4 = big.NewInt(0) -var to_6to4 = big.NewInt(0) -var from_teredo = big.NewInt(0) -var to_teredo = big.NewInt(0) -var last_32bits = big.NewInt(4294967295) - -const countryshort uint32 = 0x000001 -const countrylong uint32 = 0x000002 -const region uint32 = 0x000004 -const city uint32 = 0x000008 -const isp uint32 = 0x000010 -const latitude uint32 = 0x000020 -const longitude uint32 = 0x000040 -const domain uint32 = 0x000080 -const zipcode uint32 = 0x000100 -const timezone uint32 = 0x000200 -const netspeed uint32 = 0x000400 -const iddcode uint32 = 0x000800 -const areacode uint32 = 0x001000 -const weatherstationcode uint32 = 0x002000 -const weatherstationname uint32 = 0x004000 -const mcc uint32 = 0x008000 -const mnc uint32 = 0x010000 -const mobilebrand uint32 = 0x020000 -const elevation uint32 = 0x040000 -const usagetype uint32 = 0x080000 -const addresstype uint32 = 0x100000 -const category uint32 = 0x200000 - -const all uint32 = countryshort | countrylong | region | city | isp | latitude | longitude | domain | zipcode | timezone | netspeed | iddcode | areacode | weatherstationcode | weatherstationname | mcc | mnc | mobilebrand | elevation | usagetype | addresstype | category +var country_position = [27]uint8{0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2} +var region_position = [27]uint8{0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3} +var city_position = [27]uint8{0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4} +var isp_position = [27]uint8{0, 0, 3, 0, 5, 0, 7, 5, 7, 0, 8, 0, 9, 0, 9, 0, 9, 0, 9, 7, 9, 0, 9, 7, 9, 9, 9} +var latitude_position = [27]uint8{0, 0, 0, 0, 0, 5, 5, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5} +var longitude_position = [27]uint8{0, 0, 0, 0, 0, 6, 6, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6} +var domain_position = [27]uint8{0, 0, 0, 0, 0, 0, 0, 6, 8, 0, 9, 0, 10, 0, 10, 0, 10, 0, 10, 8, 10, 0, 10, 8, 10, 10, 10} +var zipcode_position = [27]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, 7, 0, 7, 7, 7, 0, 7, 0, 7, 7, 7, 0, 7, 7, 7} +var timezone_position = [27]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 7, 8, 8, 8, 7, 8, 0, 8, 8, 8, 0, 8, 8, 8} +var netspeed_position = [27]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 11, 0, 11, 8, 11, 0, 11, 0, 11, 0, 11, 11, 11} +var iddcode_position = [27]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 12, 0, 12, 0, 12, 9, 12, 0, 12, 12, 12} +var areacode_position = [27]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 13, 0, 13, 0, 13, 10, 13, 0, 13, 13, 13} +var weatherstationcode_position = [27]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 14, 0, 14, 0, 14, 0, 14, 14, 14} +var weatherstationname_position = [27]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 15, 0, 15, 0, 15, 0, 15, 15, 15} +var mcc_position = [27]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 16, 0, 16, 9, 16, 16, 16} +var mnc_position = [27]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 17, 0, 17, 10, 17, 17, 17} +var mobilebrand_position = [27]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 18, 0, 18, 11, 18, 18, 18} +var elevation_position = [27]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 19, 0, 19, 19, 19} +var usagetype_position = [27]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 20, 20, 20} +var addresstype_position = [27]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21} +var category_position = [27]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 22} +var district_position = [27]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23} +var asn_position = [27]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24} +var as_position = [27]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25} + +const api_version string = "9.6.0" + +var max_ipv4_range = uint128.From64(4294967295) +var max_ipv6_range = uint128.From64(0) +var from_v4mapped = uint128.From64(281470681743360) +var to_v4mapped = uint128.From64(281474976710655) +var from_6to4 = uint128.From64(0) +var to_6to4 = uint128.From64(0) +var from_teredo = uint128.From64(0) +var to_teredo = uint128.From64(0) +var last_32bits = uint128.From64(4294967295) + +const countryshort uint32 = 0x0000001 +const countrylong uint32 = 0x0000002 +const region uint32 = 0x0000004 +const city uint32 = 0x0000008 +const isp uint32 = 0x0000010 +const latitude uint32 = 0x0000020 +const longitude uint32 = 0x0000040 +const domain uint32 = 0x0000080 +const zipcode uint32 = 0x0000100 +const timezone uint32 = 0x0000200 +const netspeed uint32 = 0x0000400 +const iddcode uint32 = 0x0000800 +const areacode uint32 = 0x0001000 +const weatherstationcode uint32 = 0x0002000 +const weatherstationname uint32 = 0x0004000 +const mcc uint32 = 0x0008000 +const mnc uint32 = 0x0010000 +const mobilebrand uint32 = 0x0020000 +const elevation uint32 = 0x0040000 +const usagetype uint32 = 0x0080000 +const addresstype uint32 = 0x0100000 +const category uint32 = 0x0200000 +const district uint32 = 0x0400000 +const asn uint32 = 0x0800000 +const as uint32 = 0x1000000 + +const all uint32 = countryshort | countrylong | region | city | isp | latitude | longitude | domain | zipcode | timezone | netspeed | iddcode | areacode | weatherstationcode | weatherstationname | mcc | mnc | mobilebrand | elevation | usagetype | addresstype | category | district | asn | as const invalid_address string = "Invalid IP address." const missing_file string = "Invalid database file." const not_supported string = "This parameter is unavailable for selected data file. Please upgrade the data file." const invalid_bin string = "Incorrect IP2Location BIN file format. Please make sure that you are using the latest IP2Location BIN file." +func reverseBytes(s []byte) { + for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { + s[i], s[j] = s[j], s[i] + } +} + // get IP type and calculate IP number; calculates index too if exists -func (d *DB) checkip(ip string) (iptype uint32, ipnum *big.Int, ipindex uint32) { +func (d *DB) checkip(ip string) (iptype uint32, ipnum uint128.Uint128, ipindex uint32) { iptype = 0 - ipnum = big.NewInt(0) - ipnumtmp := big.NewInt(0) + ipnum = uint128.From64(0) + ipnumtmp := uint128.From64(0) ipindex = 0 ipaddress := net.ParseIP(ip) @@ -197,43 +223,44 @@ func (d *DB) checkip(ip string) (iptype uint32, ipnum *big.Int, ipindex uint32) if v4 != nil { iptype = 4 - ipnum.SetBytes(v4) + ipnum = uint128.From64(uint64(binary.BigEndian.Uint32(v4))) } else { v6 := ipaddress.To16() if v6 != nil { iptype = 6 - ipnum.SetBytes(v6) + reverseBytes(v6) + ipnum = uint128.FromBytes(v6) if ipnum.Cmp(from_v4mapped) >= 0 && ipnum.Cmp(to_v4mapped) <= 0 { // ipv4-mapped ipv6 should treat as ipv4 and read ipv4 data section iptype = 4 - ipnum.Sub(ipnum, from_v4mapped) + ipnum = ipnum.Sub(from_v4mapped) } else if ipnum.Cmp(from_6to4) >= 0 && ipnum.Cmp(to_6to4) <= 0 { // 6to4 so need to remap to ipv4 iptype = 4 - ipnum.Rsh(ipnum, 80) - ipnum.And(ipnum, last_32bits) + ipnum = ipnum.Rsh(80) + ipnum = ipnum.And(last_32bits) } else if ipnum.Cmp(from_teredo) >= 0 && ipnum.Cmp(to_teredo) <= 0 { // Teredo so need to remap to ipv4 iptype = 4 - ipnum.Not(ipnum) - ipnum.And(ipnum, last_32bits) + ipnum = uint128.Uint128{^ipnum.Lo, ^ipnum.Hi} + ipnum = ipnum.And(last_32bits) } } } } if iptype == 4 { - if d.meta.ipv4indexbaseaddr > 0 { - ipnumtmp.Rsh(ipnum, 16) - ipnumtmp.Lsh(ipnumtmp, 3) - ipindex = uint32(ipnumtmp.Add(ipnumtmp, big.NewInt(int64(d.meta.ipv4indexbaseaddr))).Uint64()) + if d.meta.ipv4indexed { + ipnumtmp = ipnum.Rsh(16) + ipnumtmp = ipnumtmp.Lsh(3) + ipindex = uint32(ipnumtmp.Add(uint128.From64(uint64(d.meta.ipv4indexbaseaddr))).Lo) } } else if iptype == 6 { - if d.meta.ipv6indexbaseaddr > 0 { - ipnumtmp.Rsh(ipnum, 112) - ipnumtmp.Lsh(ipnumtmp, 3) - ipindex = uint32(ipnumtmp.Add(ipnumtmp, big.NewInt(int64(d.meta.ipv6indexbaseaddr))).Uint64()) + if d.meta.ipv6indexed { + ipnumtmp = ipnum.Rsh(112) + ipnumtmp = ipnumtmp.Lsh(3) + ipindex = uint32(ipnumtmp.Add(uint128.From64(uint64(d.meta.ipv6indexbaseaddr))).Lo) } } return @@ -251,6 +278,17 @@ func (d *DB) readuint8(pos int64) (uint8, error) { return retval, nil } +// read row +func (d *DB) read_row(pos uint32, size uint32) ([]byte, error) { + pos2 := int64(pos) + data := make([]byte, size) + _, err := d.f.ReadAt(data, pos2-1) + if err != nil { + return nil, err + } + return data, nil +} + // read unsigned 32-bit integer from slices func (d *DB) readuint32_row(row []byte, pos uint32) uint32 { var retval uint32 @@ -276,40 +314,49 @@ func (d *DB) readuint32(pos uint32) (uint32, error) { return retval, nil } +// read unsigned 128-bit integer from slices +func (d *DB) readuint128_row(row []byte, pos uint32) uint128.Uint128 { + retval := uint128.From64(0) + data := row[pos : pos+16] + + // little endian to big endian + // for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 { + // data[i], data[j] = data[j], data[i] + // } + retval = uint128.FromBytes(data) + return retval +} + // read unsigned 128-bit integer -func (d *DB) readuint128(pos uint32) (*big.Int, error) { +func (d *DB) readuint128(pos uint32) (uint128.Uint128, error) { pos2 := int64(pos) - retval := big.NewInt(0) + retval := uint128.From64(0) data := make([]byte, 16) _, err := d.f.ReadAt(data, pos2-1) if err != nil { - return nil, err + return uint128.From64(0), err } // little endian to big endian - for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 { - data[i], data[j] = data[j], data[i] - } - retval.SetBytes(data) + // for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 { + // data[i], data[j] = data[j], data[i] + // } + retval = uint128.FromBytes(data) return retval, nil } // read string func (d *DB) readstr(pos uint32) (string, error) { pos2 := int64(pos) + readlen := 256 // max size of string field + 1 byte for the length var retval string - lenbyte := make([]byte, 1) - _, err := d.f.ReadAt(lenbyte, pos2) - if err != nil { - return "", err - } - strlen := lenbyte[0] - data := make([]byte, strlen) - _, err = d.f.ReadAt(data, pos2+1) - if err != nil { + data := make([]byte, readlen) + _, err := d.f.ReadAt(data, pos2) + if err != nil && err.Error() != "EOF" { // bypass EOF error coz we are reading 256 which may hit EOF return "", err } - retval = string(data[:strlen]) + strlen := data[0] + retval = convertBytesToString(data[1:(strlen + 1)]) return retval, nil } @@ -343,75 +390,64 @@ func OpenDB(dbpath string) (*DB, error) { func OpenDBWithReader(reader DBReader) (*DB, error) { var db = &DB{} - max_ipv6_range.SetString("340282366920938463463374607431768211455", 10) - from_6to4.SetString("42545680458834377588178886921629466624", 10) - to_6to4.SetString("42550872755692912415807417417958686719", 10) - from_teredo.SetString("42540488161975842760550356425300246528", 10) - to_teredo.SetString("42540488241204005274814694018844196863", 10) + _max_ipv6_range := big.NewInt(0) + _max_ipv6_range.SetString("340282366920938463463374607431768211455", 10) + max_ipv6_range = uint128.FromBig(_max_ipv6_range) + + _from_6to4 := big.NewInt(0) + _from_6to4.SetString("42545680458834377588178886921629466624", 10) + from_6to4 = uint128.FromBig(_from_6to4) + + _to_6to4 := big.NewInt(0) + _to_6to4.SetString("42550872755692912415807417417958686719", 10) + to_6to4 = uint128.FromBig(_to_6to4) + + _from_teredo := big.NewInt(0) + _from_teredo.SetString("42540488161975842760550356425300246528", 10) + from_teredo = uint128.FromBig(_from_teredo) + + _to_teredo := big.NewInt(0) + _to_teredo.SetString("42540488241204005274814694018844196863", 10) + to_teredo = uint128.FromBig(_to_teredo) db.f = reader + var row []byte var err error - db.meta.databasetype, err = db.readuint8(1) - if err != nil { - return fatal(db, err) - } - db.meta.databasecolumn, err = db.readuint8(2) - if err != nil { - return fatal(db, err) - } - db.meta.databaseyear, err = db.readuint8(3) - if err != nil { - return fatal(db, err) - } - db.meta.databasemonth, err = db.readuint8(4) - if err != nil { - return fatal(db, err) - } - db.meta.databaseday, err = db.readuint8(5) - if err != nil { - return fatal(db, err) - } - db.meta.ipv4databasecount, err = db.readuint32(6) - if err != nil { - return fatal(db, err) - } - db.meta.ipv4databaseaddr, err = db.readuint32(10) - if err != nil { - return fatal(db, err) - } - db.meta.ipv6databasecount, err = db.readuint32(14) - if err != nil { - return fatal(db, err) - } - db.meta.ipv6databaseaddr, err = db.readuint32(18) - if err != nil { - return fatal(db, err) - } - db.meta.ipv4indexbaseaddr, err = db.readuint32(22) - if err != nil { - return fatal(db, err) - } - db.meta.ipv6indexbaseaddr, err = db.readuint32(26) - if err != nil { - return fatal(db, err) - } - db.meta.productcode, err = db.readuint8(30) - if err != nil { - return fatal(db, err) - } - db.meta.producttype, err = db.readuint8(31) - if err != nil { - return fatal(db, err) - } - db.meta.filesize, err = db.readuint32(32) + readlen := uint32(64) // 64-byte header + + row, err = db.read_row(1, readlen) if err != nil { return fatal(db, err) } + db.meta.databasetype = row[0] + db.meta.databasecolumn = row[1] + db.meta.databaseyear = row[2] + db.meta.databasemonth = row[3] + db.meta.databaseday = row[4] + db.meta.ipv4databasecount = db.readuint32_row(row, 5) + db.meta.ipv4databaseaddr = db.readuint32_row(row, 9) + db.meta.ipv6databasecount = db.readuint32_row(row, 13) + db.meta.ipv6databaseaddr = db.readuint32_row(row, 17) + db.meta.ipv4indexbaseaddr = db.readuint32_row(row, 21) + db.meta.ipv6indexbaseaddr = db.readuint32_row(row, 25) + db.meta.productcode = row[29] + db.meta.producttype = row[30] + db.meta.filesize = db.readuint32_row(row, 31) + // check if is correct BIN (should be 1 for IP2Location BIN file), also checking for zipped file (PK being the first 2 chars) if (db.meta.productcode != 1 && db.meta.databaseyear >= 21) || (db.meta.databasetype == 80 && db.meta.databasecolumn == 75) { // only BINs from Jan 2021 onwards have this byte set return fatal(db, errors.New(invalid_bin)) } + + if db.meta.ipv4indexbaseaddr > 0 { + db.meta.ipv4indexed = true + } + + if db.meta.ipv6databasecount > 0 && db.meta.ipv6indexbaseaddr > 0 { + db.meta.ipv6indexed = true + } + db.meta.ipv4columnsize = uint32(db.meta.databasecolumn << 2) // 4 bytes each column db.meta.ipv6columnsize = uint32(16 + ((db.meta.databasecolumn - 1) << 2)) // 4 bytes each column, except IPFrom column which is 16 bytes @@ -501,6 +537,18 @@ func OpenDBWithReader(reader DBReader) (*DB, error) { db.category_position_offset = uint32(category_position[dbt]-2) << 2 db.category_enabled = true } + if district_position[dbt] != 0 { + db.district_position_offset = uint32(district_position[dbt]-2) << 2 + db.district_enabled = true + } + if asn_position[dbt] != 0 { + db.asn_position_offset = uint32(asn_position[dbt]-2) << 2 + db.asn_enabled = true + } + if as_position[dbt] != 0 { + db.as_position_offset = uint32(as_position[dbt]-2) << 2 + db.as_enabled = true + } db.metaok = true @@ -555,6 +603,9 @@ func loadmessage(mesg string) IP2Locationrecord { x.Usagetype = mesg x.Addresstype = mesg x.Category = mesg + x.District = mesg + x.Asn = mesg + x.As = mesg return x } @@ -566,6 +617,13 @@ func handleError(rec IP2Locationrecord, err error) IP2Locationrecord { return rec } +// convertBytesToString provides a no-copy []byte to string conversion. +// This implementation is adopted by official strings.Builder. +// Reference: https://github.com/golang/go/issues/25484 +func convertBytesToString(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} + // Get_all will return all geolocation fields based on the queried IP address. // // Deprecated: No longer being updated. @@ -828,6 +886,21 @@ func (d *DB) Get_category(ipaddress string) (IP2Locationrecord, error) { return d.query(ipaddress, category) } +// Get_district will return the district name based on the queried IP address. +func (d *DB) Get_district(ipaddress string) (IP2Locationrecord, error) { + return d.query(ipaddress, district) +} + +// Get_asn will return the autonomous system number (ASN) based on the queried IP address. +func (d *DB) Get_asn(ipaddress string) (IP2Locationrecord, error) { + return d.query(ipaddress, asn) +} + +// Get_as will return the autonomous system (AS) based on the queried IP address. +func (d *DB) Get_as(ipaddress string) (IP2Locationrecord, error) { + return d.query(ipaddress, as) +} + // main query func (d *DB) query(ipaddress string, mode uint32) (IP2Locationrecord, error) { x := loadmessage(not_supported) // default message @@ -853,10 +926,13 @@ func (d *DB) query(ipaddress string, mode uint32) (IP2Locationrecord, error) { var high uint32 var mid uint32 var rowoffset uint32 - var rowoffset2 uint32 - ipfrom := big.NewInt(0) - ipto := big.NewInt(0) - maxip := big.NewInt(0) + var firstcol uint32 = 4 // 4 bytes for ip from + var row []byte + var fullrow []byte + var readlen uint32 + ipfrom := uint128.From64(0) + ipto := uint128.From64(0) + maxip := uint128.From64(0) if iptype == 4 { baseaddr = d.meta.ipv4databaseaddr @@ -864,6 +940,7 @@ func (d *DB) query(ipaddress string, mode uint32) (IP2Locationrecord, error) { maxip = max_ipv4_range colsize = d.meta.ipv4columnsize } else { + firstcol = 16 // 16 bytes for ip from baseaddr = d.meta.ipv6databaseaddr high = d.meta.ipv6databasecount maxip = max_ipv6_range @@ -872,61 +949,44 @@ func (d *DB) query(ipaddress string, mode uint32) (IP2Locationrecord, error) { // reading index if ipindex > 0 { - low, err = d.readuint32(ipindex) - if err != nil { - return x, err - } - high, err = d.readuint32(ipindex + 4) + row, err = d.read_row(ipindex, 8) // 4 bytes each for IP From and IP To if err != nil { return x, err } + low = d.readuint32_row(row, 0) + high = d.readuint32_row(row, 4) } if ipno.Cmp(maxip) >= 0 { - ipno.Sub(ipno, big.NewInt(1)) + ipno = ipno.Sub(uint128.From64(1)) } for low <= high { mid = ((low + high) >> 1) rowoffset = baseaddr + (mid * colsize) - rowoffset2 = rowoffset + colsize - if iptype == 4 { - ipfrom32, err := d.readuint32(rowoffset) - if err != nil { - return x, err - } - ipfrom = big.NewInt(int64(ipfrom32)) + // reading IP From + whole row + next IP From + readlen = colsize + firstcol + fullrow, err = d.read_row(rowoffset, readlen) + if err != nil { + return x, err + } - ipto32, err := d.readuint32(rowoffset2) - if err != nil { - return x, err - } - ipto = big.NewInt(int64(ipto32)) + if iptype == 4 { + ipfrom32 := d.readuint32_row(fullrow, 0) + ipfrom = uint128.From64(uint64(ipfrom32)) + ipto32 := d.readuint32_row(fullrow, colsize) + ipto = uint128.From64(uint64(ipto32)) } else { - ipfrom, err = d.readuint128(rowoffset) - if err != nil { - return x, err - } + ipfrom = d.readuint128_row(fullrow, 0) - ipto, err = d.readuint128(rowoffset2) - if err != nil { - return x, err - } + ipto = d.readuint128_row(fullrow, colsize) } if ipno.Cmp(ipfrom) >= 0 && ipno.Cmp(ipto) < 0 { - var firstcol uint32 = 4 // 4 bytes for ip from - if iptype == 6 { - firstcol = 16 // 16 bytes for ipv6 - } - - row := make([]byte, colsize-firstcol) // exclude the ip from field - _, err := d.f.ReadAt(row, int64(rowoffset+firstcol-1)) - if err != nil { - return x, err - } + rowlen := colsize - firstcol + row = fullrow[firstcol:(firstcol + rowlen)] // extract the actual row data if mode&countryshort == 1 && d.country_enabled { if x.Country_short, err = d.readstr(d.readuint32_row(row, d.country_position_offset)); err != nil { @@ -1060,6 +1120,24 @@ func (d *DB) query(ipaddress string, mode uint32) (IP2Locationrecord, error) { } } + if mode&district != 0 && d.district_enabled { + if x.District, err = d.readstr(d.readuint32_row(row, d.district_position_offset)); err != nil { + return x, err + } + } + + if mode&asn != 0 && d.asn_enabled { + if x.Asn, err = d.readstr(d.readuint32_row(row, d.asn_position_offset)); err != nil { + return x, err + } + } + + if mode&as != 0 && d.as_enabled { + if x.As, err = d.readstr(d.readuint32_row(row, d.as_position_offset)); err != nil { + return x, err + } + } + return x, nil } else { if ipno.Cmp(ipfrom) < 0 { @@ -1100,4 +1178,7 @@ func Printrecord(x IP2Locationrecord) { fmt.Printf("usagetype: %s\n", x.Usagetype) fmt.Printf("addresstype: %s\n", x.Addresstype) fmt.Printf("category: %s\n", x.Category) + fmt.Printf("district: %s\n", x.District) + fmt.Printf("asn: %s\n", x.Asn) + fmt.Printf("as: %s\n", x.As) } diff --git a/vendor/github.com/ip2location/ip2location-go/v9/iptools.go b/vendor/github.com/ip2location/ip2location-go/v9/iptools.go new file mode 100644 index 000000000..5b9786f56 --- /dev/null +++ b/vendor/github.com/ip2location/ip2location-go/v9/iptools.go @@ -0,0 +1,479 @@ +package ip2location + +import ( + "encoding/hex" + "errors" + "fmt" + "math" + "math/big" + "net" + "regexp" + "sort" + "strconv" + "strings" +) + +// The IPTools struct is the main object to access the IP address tools +type IPTools struct { + max_ipv4_range *big.Int + max_ipv6_range *big.Int +} + +// OpenTools initializes some variables +func OpenTools() *IPTools { + var t = &IPTools{} + t.max_ipv4_range = big.NewInt(4294967295) + t.max_ipv6_range = big.NewInt(0) + t.max_ipv6_range.SetString("340282366920938463463374607431768211455", 10) + return t +} + +// IsIPv4 returns true if the IP address provided is an IPv4. +func (t *IPTools) IsIPv4(IP string) bool { + ipaddr := net.ParseIP(IP) + + if ipaddr == nil { + return false + } + + v4 := ipaddr.To4() + + if v4 == nil { + return false + } + + return true +} + +// IsIPv6 returns true if the IP address provided is an IPv6. +func (t *IPTools) IsIPv6(IP string) bool { + if t.IsIPv4(IP) { + return false + } + + ipaddr := net.ParseIP(IP) + + if ipaddr == nil { + return false + } + + v6 := ipaddr.To16() + + if v6 == nil { + return false + } + + return true +} + +// IPv4ToDecimal returns the IP number for the supplied IPv4 address. +func (t *IPTools) IPv4ToDecimal(IP string) (*big.Int, error) { + if !t.IsIPv4(IP) { + return nil, errors.New("Not a valid IPv4 address.") + } + + ipnum := big.NewInt(0) + ipaddr := net.ParseIP(IP) + + if ipaddr != nil { + v4 := ipaddr.To4() + + if v4 != nil { + ipnum.SetBytes(v4) + } + } + + return ipnum, nil +} + +// IPv6ToDecimal returns the IP number for the supplied IPv6 address. +func (t *IPTools) IPv6ToDecimal(IP string) (*big.Int, error) { + if !t.IsIPv6(IP) { + return nil, errors.New("Not a valid IPv6 address.") + } + + ipnum := big.NewInt(0) + ipaddr := net.ParseIP(IP) + + if ipaddr != nil { + v6 := ipaddr.To16() + + if v6 != nil { + ipnum.SetBytes(v6) + } + } + + return ipnum, nil +} + +// DecimalToIPv4 returns the IPv4 address for the supplied IP number. +func (t *IPTools) DecimalToIPv4(IPNum *big.Int) (string, error) { + if IPNum.Cmp(big.NewInt(0)) < 0 || IPNum.Cmp(t.max_ipv4_range) > 0 { + return "", errors.New("Invalid IP number.") + } + + buf := make([]byte, 4) + bytes := IPNum.FillBytes(buf) + + ip := net.IP(bytes) + return ip.String(), nil +} + +// DecimalToIPv6 returns the IPv6 address for the supplied IP number. +func (t *IPTools) DecimalToIPv6(IPNum *big.Int) (string, error) { + if IPNum.Cmp(big.NewInt(0)) < 0 || IPNum.Cmp(t.max_ipv6_range) > 0 { + return "", errors.New("Invalid IP number.") + } + + buf := make([]byte, 16) + bytes := IPNum.FillBytes(buf) + + ip := net.IP(bytes) + return ip.String(), nil +} + +// CompressIPv6 returns the compressed form of the supplied IPv6 address. +func (t *IPTools) CompressIPv6(IP string) (string, error) { + if !t.IsIPv6(IP) { + return "", errors.New("Not a valid IPv6 address.") + } + + ipaddr := net.ParseIP(IP) + + if ipaddr == nil { + return "", errors.New("Not a valid IPv6 address.") + } + + return ipaddr.String(), nil +} + +// ExpandIPv6 returns the expanded form of the supplied IPv6 address. +func (t *IPTools) ExpandIPv6(IP string) (string, error) { + if !t.IsIPv6(IP) { + return "", errors.New("Not a valid IPv6 address.") + } + + ipaddr := net.ParseIP(IP) + + ipstr := hex.EncodeToString(ipaddr) + re := regexp.MustCompile(`(.{4})`) + ipstr = re.ReplaceAllString(ipstr, "$1:") + ipstr = strings.TrimSuffix(ipstr, ":") + + return ipstr, nil +} + +// IPv4ToCIDR returns the CIDR for the supplied IPv4 range. +func (t *IPTools) IPv4ToCIDR(IPFrom string, IPTo string) ([]string, error) { + if !t.IsIPv4(IPFrom) || !t.IsIPv4(IPTo) { + return nil, errors.New("Not a valid IPv4 address.") + } + + startipbig, _ := t.IPv4ToDecimal(IPFrom) + endipbig, _ := t.IPv4ToDecimal(IPTo) + startip := startipbig.Uint64() + endip := endipbig.Uint64() + var result []string + var maxsize float64 + var maxdiff float64 + + for endip >= startip { + maxsize = 32 + + for maxsize > 0 { + mask := math.Pow(2, 32) - math.Pow(2, 32-(maxsize-1)) + maskbase := startip & uint64(mask) + + if maskbase != startip { + break + } + + maxsize = maxsize - 1 + } + + x := math.Log(float64(endip)-float64(startip)+1) / math.Log(2) + maxdiff = 32 - math.Floor(x) + + if maxsize < maxdiff { + maxsize = maxdiff + } + + bn := big.NewInt(0) + + bn.SetString(fmt.Sprintf("%v", startip), 10) + + ip, _ := t.DecimalToIPv4(bn) + result = append(result, ip+"/"+fmt.Sprintf("%v", maxsize)) + startip = startip + uint64(math.Pow(2, 32-maxsize)) + } + + return result, nil +} + +// converts IPv6 address to binary string representation. +func (t *IPTools) ipToBinary(ip string) (string, error) { + if !t.IsIPv6(ip) { + return "", errors.New("Not a valid IPv6 address.") + } + + ipaddr := net.ParseIP(ip) + + binstr := "" + for i, j := 0, len(ipaddr); i < j; i = i + 1 { + binstr += fmt.Sprintf("%08b", ipaddr[i]) + } + + return binstr, nil +} + +// converts binary string representation to IPv6 address. +func (t *IPTools) binaryToIP(binstr string) (string, error) { + re := regexp.MustCompile(`^[01]{128}$`) + if !re.MatchString(binstr) { + return "", errors.New("Not a valid binary string.") + } + + re2 := regexp.MustCompile(`(.{8})`) + + bytes := make([]byte, 16) + i := 0 + matches := re2.FindAllStringSubmatch(binstr, -1) + for _, v := range matches { + x, _ := strconv.ParseUint(v[1], 2, 8) + bytes[i] = byte(x) + i = i + 1 + } + + ipaddr := net.IP(bytes) + + return ipaddr.String(), nil +} + +// returns the min and max for the array +func (t *IPTools) minMax(array []int) (int, int) { + var max int = array[0] + var min int = array[0] + for _, value := range array { + if max < value { + max = value + } + if min > value { + min = value + } + } + return min, max +} + +// IPv6ToCIDR returns the CIDR for the supplied IPv6 range. +func (t *IPTools) IPv6ToCIDR(IPFrom string, IPTo string) ([]string, error) { + if !t.IsIPv6(IPFrom) || !t.IsIPv6(IPTo) { + return nil, errors.New("Not a valid IPv6 address.") + } + + ipfrombin, err := t.ipToBinary(IPFrom) + + if err != nil { + return nil, errors.New("Not a valid IPv6 address.") + } + + iptobin, err := t.ipToBinary(IPTo) + + if err != nil { + return nil, errors.New("Not a valid IPv6 address.") + } + + var result []string + + networksize := 0 + shift := 0 + unpadded := "" + padded := "" + networks := make(map[string]int) + n := 0 + + if ipfrombin == iptobin { + result = append(result, IPFrom+"/128") + return result, nil + } + + if ipfrombin > iptobin { + tmp := ipfrombin + ipfrombin = iptobin + iptobin = tmp + } + + for { + if string(ipfrombin[len(ipfrombin)-1]) == "1" { + unpadded = ipfrombin[networksize:128] + padded = fmt.Sprintf("%-128s", unpadded) // pad right with spaces + padded = strings.ReplaceAll(padded, " ", "0") // replace spaces + networks[padded] = 128 - networksize + n = strings.LastIndex(ipfrombin, "0") + if n == 0 { + ipfrombin = "" + } else { + ipfrombin = ipfrombin[0:n] + } + ipfrombin = ipfrombin + "1" + ipfrombin = fmt.Sprintf("%-128s", ipfrombin) // pad right with spaces + ipfrombin = strings.ReplaceAll(ipfrombin, " ", "0") // replace spaces + } + + if string(iptobin[len(iptobin)-1]) == "0" { + unpadded = iptobin[networksize:128] + padded = fmt.Sprintf("%-128s", unpadded) // pad right with spaces + padded = strings.ReplaceAll(padded, " ", "0") // replace spaces + networks[padded] = 128 - networksize + n = strings.LastIndex(iptobin, "1") + if n == 0 { + iptobin = "" + } else { + iptobin = iptobin[0:n] + } + iptobin = iptobin + "0" + iptobin = fmt.Sprintf("%-128s", iptobin) // pad right with spaces + iptobin = strings.ReplaceAll(iptobin, " ", "1") // replace spaces + } + + if iptobin < ipfrombin { + // special logic for Go due to lack of do-while + if ipfrombin >= iptobin { + break + } + continue + } + + values := []int{strings.LastIndex(ipfrombin, "0"), strings.LastIndex(iptobin, "1")} + _, max := t.minMax(values) + shift = 128 - max + unpadded = ipfrombin[0 : 128-shift] + ipfrombin = fmt.Sprintf("%0128s", unpadded) + unpadded = iptobin[0 : 128-shift] + iptobin = fmt.Sprintf("%0128s", unpadded) + + networksize = networksize + shift + + if ipfrombin == iptobin { + unpadded = ipfrombin[networksize:128] + padded = fmt.Sprintf("%-128s", unpadded) // pad right with spaces + padded = strings.ReplaceAll(padded, " ", "0") // replace spaces + networks[padded] = 128 - networksize + } + + if ipfrombin >= iptobin { + break + } + } + + keys := make([]string, 0, len(networks)) + for k := range networks { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, k := range keys { + str, _ := t.binaryToIP(k) + result = append(result, str+"/"+fmt.Sprintf("%d", networks[k])) + } + + return result, nil +} + +// CIDRToIPv4 returns the IPv4 range for the supplied CIDR. +func (t *IPTools) CIDRToIPv4(CIDR string) ([]string, error) { + if strings.Index(CIDR, "/") == -1 { + return nil, errors.New("Not a valid CIDR.") + } + + re := regexp.MustCompile(`^[0-9]{1,2}$`) + arr := strings.Split(CIDR, "/") + + if len(arr) != 2 || !t.IsIPv4(arr[0]) || !re.MatchString(arr[1]) { + return nil, errors.New("Not a valid CIDR.") + } + + ip := arr[0] + + prefix, err := strconv.Atoi(arr[1]) + if err != nil || prefix > 32 { + return nil, errors.New("Not a valid CIDR.") + } + + ipstartbn, err := t.IPv4ToDecimal(ip) + if err != nil { + return nil, errors.New("Not a valid CIDR.") + } + ipstartlong := ipstartbn.Int64() + + ipstartlong = ipstartlong & (-1 << (32 - prefix)) + + bn := big.NewInt(0) + bn.SetString(strconv.Itoa(int(ipstartlong)), 10) + + ipstart, _ := t.DecimalToIPv4(bn) + + var total int64 = 1 << (32 - prefix) + + ipendlong := ipstartlong + total - 1 + + if ipendlong > 4294967295 { + ipendlong = 4294967295 + } + + bn.SetString(strconv.Itoa(int(ipendlong)), 10) + ipend, _ := t.DecimalToIPv4(bn) + + result := []string{ipstart, ipend} + + return result, nil +} + +// CIDRToIPv6 returns the IPv6 range for the supplied CIDR. +func (t *IPTools) CIDRToIPv6(CIDR string) ([]string, error) { + if strings.Index(CIDR, "/") == -1 { + return nil, errors.New("Not a valid CIDR.") + } + + re := regexp.MustCompile(`^[0-9]{1,3}$`) + arr := strings.Split(CIDR, "/") + + if len(arr) != 2 || !t.IsIPv6(arr[0]) || !re.MatchString(arr[1]) { + return nil, errors.New("Not a valid CIDR.") + } + + ip := arr[0] + + prefix, err := strconv.Atoi(arr[1]) + if err != nil || prefix > 128 { + return nil, errors.New("Not a valid CIDR.") + } + + hexstartaddress, _ := t.ExpandIPv6(ip) + hexstartaddress = strings.ReplaceAll(hexstartaddress, ":", "") + hexendaddress := hexstartaddress + + bits := 128 - prefix + pos := 31 + + for bits > 0 { + values := []int{4, bits} + min, _ := t.minMax(values) + x, _ := strconv.ParseInt(string(hexendaddress[pos]), 16, 64) + y := fmt.Sprintf("%x", (x | int64(math.Pow(2, float64(min))-1))) + + hexendaddress = hexendaddress[:pos] + y + hexendaddress[pos+1:] + + bits = bits - 4 + pos = pos - 1 + } + + re2 := regexp.MustCompile(`(.{4})`) + hexstartaddress = re2.ReplaceAllString(hexstartaddress, "$1:") + hexstartaddress = strings.TrimSuffix(hexstartaddress, ":") + hexendaddress = re2.ReplaceAllString(hexendaddress, "$1:") + hexendaddress = strings.TrimSuffix(hexendaddress, ":") + + result := []string{hexstartaddress, hexendaddress} + + return result, nil +} diff --git a/vendor/github.com/ip2location/ip2location-go/v9/region.go b/vendor/github.com/ip2location/ip2location-go/v9/region.go new file mode 100644 index 000000000..c06e3303e --- /dev/null +++ b/vendor/github.com/ip2location/ip2location-go/v9/region.go @@ -0,0 +1,96 @@ +package ip2location + +import ( + "encoding/csv" + "errors" + "os" + "strings" +) + +// The RegionInfoRecord struct stores all of the available +// region info found in the region information CSV file. +type RegionInfoRecord struct { + Country_code string + Name string + Code string +} + +// The RI struct is the main object used to read the region information CSV. +type RI struct { + resultsMap map[string][]RegionInfoRecord +} + +// OpenRegionInfo initializes with the path to the region information CSV file. +func OpenRegionInfo(csvFile string) (*RI, error) { + var ri = &RI{} + + _, err := os.Stat(csvFile) + if os.IsNotExist(err) { + return nil, errors.New("The CSV file '" + csvFile + "' is not found.") + } + + f, err := os.Open(csvFile) + if err != nil { + return nil, errors.New("Unable to read '" + csvFile + "'.") + } + + defer f.Close() + + csvReader := csv.NewReader(f) + data, err := csvReader.ReadAll() + if err != nil { + return nil, errors.New("Unable to read '" + csvFile + "'.") + } + + ri.resultsMap = make(map[string][]RegionInfoRecord) + var headerArr []string + var resultsArr []RegionInfoRecord + + for i, line := range data { + if i == 0 { // headers + for _, field := range line { + headerArr = append(headerArr, field) + } + } else { + var rec RegionInfoRecord + for j, field := range line { + switch headerArr[j] { + case "country_code": + rec.Country_code = field + case "subdivision_name": + rec.Name = field + case "code": + rec.Code = field + } + } + if rec.Name == "" { + return nil, errors.New("Invalid region information CSV file.") + } + resultsArr = append(resultsArr, rec) + } + } + for _, elem := range resultsArr { + if _, ok := ri.resultsMap[elem.Country_code]; !ok { + var arr []RegionInfoRecord + ri.resultsMap[elem.Country_code] = arr + } + ri.resultsMap[elem.Country_code] = append(ri.resultsMap[elem.Country_code], elem) + } + return ri, nil +} + +// GetRegionCode returns the region code for the specified country and region name +func (r *RI) GetRegionCode(countryCode string, regionName string) (string, error) { + if len(r.resultsMap) == 0 { + return "", errors.New("No record available.") + } + + if arr, ok := r.resultsMap[countryCode]; ok { + for _, elem := range arr { + if strings.ToUpper(elem.Name) == strings.ToUpper(regionName) { + return elem.Code, nil + } + } + } + return "", errors.New("No record found.") +} diff --git a/vendor/lukechampine.com/uint128/LICENSE b/vendor/lukechampine.com/uint128/LICENSE new file mode 100644 index 000000000..a14c6cf2a --- /dev/null +++ b/vendor/lukechampine.com/uint128/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019 Luke Champine + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/lukechampine.com/uint128/README.md b/vendor/lukechampine.com/uint128/README.md new file mode 100644 index 000000000..1ea5d7df1 --- /dev/null +++ b/vendor/lukechampine.com/uint128/README.md @@ -0,0 +1,46 @@ +uint128 +------- + +[![GoDoc](https://godoc.org/github.com/lukechampine/uint128?status.svg)](https://godoc.org/github.com/lukechampine/uint128) +[![Go Report Card](http://goreportcard.com/badge/github.com/lukechampine/uint128)](https://goreportcard.com/report/github.com/lukechampine/uint128) + +``` +go get lukechampine.com/uint128 +``` + +`uint128` provides a high-performance `Uint128` type that supports standard arithmetic +operations. Unlike `math/big`, operations on `Uint128` values always produce new values +instead of modifying a pointer receiver. A `Uint128` value is therefore immutable, just +like `uint64` and friends. + +The name `uint128.Uint128` stutters, so I recommend either using a "dot import" +or aliasing `uint128.Uint128` to give it a project-specific name. Embedding the type +is not recommended, because methods will still return `uint128.Uint128`; this means that, +if you want to extend the type with new methods, your best bet is probably to copy the +source code wholesale and rename the identifier. ¯\\\_(ツ)\_/¯ + + +# Benchmarks + +Addition, multiplication, and subtraction are on par with their native 64-bit +equivalents. Division is slower: ~20x slower when dividing a `Uint128` by a +`uint64`, and ~100x slower when dividing by a `Uint128`. However, division is +still faster than with `big.Int` (for the same operands), especially when +dividing by a `uint64`. + +``` +BenchmarkArithmetic/Add-4 2000000000 0.45 ns/op 0 B/op 0 allocs/op +BenchmarkArithmetic/Sub-4 2000000000 0.67 ns/op 0 B/op 0 allocs/op +BenchmarkArithmetic/Mul-4 2000000000 0.42 ns/op 0 B/op 0 allocs/op +BenchmarkArithmetic/Lsh-4 2000000000 1.06 ns/op 0 B/op 0 allocs/op +BenchmarkArithmetic/Rsh-4 2000000000 1.06 ns/op 0 B/op 0 allocs/op + +BenchmarkDivision/native_64/64-4 2000000000 0.39 ns/op 0 B/op 0 allocs/op +BenchmarkDivision/Div_128/64-4 2000000000 6.28 ns/op 0 B/op 0 allocs/op +BenchmarkDivision/Div_128/128-4 30000000 45.2 ns/op 0 B/op 0 allocs/op +BenchmarkDivision/big.Int_128/64-4 20000000 98.2 ns/op 8 B/op 1 allocs/op +BenchmarkDivision/big.Int_128/128-4 30000000 53.4 ns/op 48 B/op 1 allocs/op + +BenchmarkString/Uint128-4 10000000 173 ns/op 48 B/op 1 allocs/op +BenchmarkString/big.Int-4 5000000 350 ns/op 144 B/op 3 allocs/op +``` diff --git a/vendor/lukechampine.com/uint128/uint128.go b/vendor/lukechampine.com/uint128/uint128.go new file mode 100644 index 000000000..04e65783a --- /dev/null +++ b/vendor/lukechampine.com/uint128/uint128.go @@ -0,0 +1,440 @@ +package uint128 // import "lukechampine.com/uint128" + +import ( + "encoding/binary" + "errors" + "fmt" + "math" + "math/big" + "math/bits" +) + +// Zero is a zero-valued uint128. +var Zero Uint128 + +// Max is the largest possible uint128 value. +var Max = New(math.MaxUint64, math.MaxUint64) + +// A Uint128 is an unsigned 128-bit number. +type Uint128 struct { + Lo, Hi uint64 +} + +// IsZero returns true if u == 0. +func (u Uint128) IsZero() bool { + // NOTE: we do not compare against Zero, because that is a global variable + // that could be modified. + return u == Uint128{} +} + +// Equals returns true if u == v. +// +// Uint128 values can be compared directly with ==, but use of the Equals method +// is preferred for consistency. +func (u Uint128) Equals(v Uint128) bool { + return u == v +} + +// Equals64 returns true if u == v. +func (u Uint128) Equals64(v uint64) bool { + return u.Lo == v && u.Hi == 0 +} + +// Cmp compares u and v and returns: +// +// -1 if u < v +// 0 if u == v +// +1 if u > v +// +func (u Uint128) Cmp(v Uint128) int { + if u == v { + return 0 + } else if u.Hi < v.Hi || (u.Hi == v.Hi && u.Lo < v.Lo) { + return -1 + } else { + return 1 + } +} + +// Cmp64 compares u and v and returns: +// +// -1 if u < v +// 0 if u == v +// +1 if u > v +// +func (u Uint128) Cmp64(v uint64) int { + if u.Hi == 0 && u.Lo == v { + return 0 + } else if u.Hi == 0 && u.Lo < v { + return -1 + } else { + return 1 + } +} + +// And returns u&v. +func (u Uint128) And(v Uint128) Uint128 { + return Uint128{u.Lo & v.Lo, u.Hi & v.Hi} +} + +// And64 returns u&v. +func (u Uint128) And64(v uint64) Uint128 { + return Uint128{u.Lo & v, u.Hi & 0} +} + +// Or returns u|v. +func (u Uint128) Or(v Uint128) Uint128 { + return Uint128{u.Lo | v.Lo, u.Hi | v.Hi} +} + +// Or64 returns u|v. +func (u Uint128) Or64(v uint64) Uint128 { + return Uint128{u.Lo | v, u.Hi | 0} +} + +// Xor returns u^v. +func (u Uint128) Xor(v Uint128) Uint128 { + return Uint128{u.Lo ^ v.Lo, u.Hi ^ v.Hi} +} + +// Xor64 returns u^v. +func (u Uint128) Xor64(v uint64) Uint128 { + return Uint128{u.Lo ^ v, u.Hi ^ 0} +} + +// Add returns u+v. +func (u Uint128) Add(v Uint128) Uint128 { + lo, carry := bits.Add64(u.Lo, v.Lo, 0) + hi, carry := bits.Add64(u.Hi, v.Hi, carry) + if carry != 0 { + panic("overflow") + } + return Uint128{lo, hi} +} + +// AddWrap returns u+v with wraparound semantics; for example, +// Max.AddWrap(From64(1)) == Zero. +func (u Uint128) AddWrap(v Uint128) Uint128 { + lo, carry := bits.Add64(u.Lo, v.Lo, 0) + hi, _ := bits.Add64(u.Hi, v.Hi, carry) + return Uint128{lo, hi} +} + +// Add64 returns u+v. +func (u Uint128) Add64(v uint64) Uint128 { + lo, carry := bits.Add64(u.Lo, v, 0) + hi, carry := bits.Add64(u.Hi, 0, carry) + if carry != 0 { + panic("overflow") + } + return Uint128{lo, hi} +} + +// AddWrap64 returns u+v with wraparound semantics; for example, +// Max.AddWrap64(1) == Zero. +func (u Uint128) AddWrap64(v uint64) Uint128 { + lo, carry := bits.Add64(u.Lo, v, 0) + hi := u.Hi + carry + return Uint128{lo, hi} +} + +// Sub returns u-v. +func (u Uint128) Sub(v Uint128) Uint128 { + lo, borrow := bits.Sub64(u.Lo, v.Lo, 0) + hi, borrow := bits.Sub64(u.Hi, v.Hi, borrow) + if borrow != 0 { + panic("underflow") + } + return Uint128{lo, hi} +} + +// SubWrap returns u-v with wraparound semantics; for example, +// Zero.SubWrap(From64(1)) == Max. +func (u Uint128) SubWrap(v Uint128) Uint128 { + lo, borrow := bits.Sub64(u.Lo, v.Lo, 0) + hi, _ := bits.Sub64(u.Hi, v.Hi, borrow) + return Uint128{lo, hi} +} + +// Sub64 returns u-v. +func (u Uint128) Sub64(v uint64) Uint128 { + lo, borrow := bits.Sub64(u.Lo, v, 0) + hi, borrow := bits.Sub64(u.Hi, 0, borrow) + if borrow != 0 { + panic("underflow") + } + return Uint128{lo, hi} +} + +// SubWrap64 returns u-v with wraparound semantics; for example, +// Zero.SubWrap64(1) == Max. +func (u Uint128) SubWrap64(v uint64) Uint128 { + lo, borrow := bits.Sub64(u.Lo, v, 0) + hi := u.Hi - borrow + return Uint128{lo, hi} +} + +// Mul returns u*v, panicking on overflow. +func (u Uint128) Mul(v Uint128) Uint128 { + hi, lo := bits.Mul64(u.Lo, v.Lo) + p0, p1 := bits.Mul64(u.Hi, v.Lo) + p2, p3 := bits.Mul64(u.Lo, v.Hi) + hi, c0 := bits.Add64(hi, p1, 0) + hi, c1 := bits.Add64(hi, p3, c0) + if (u.Hi != 0 && v.Hi != 0) || p0 != 0 || p2 != 0 || c1 != 0 { + panic("overflow") + } + return Uint128{lo, hi} +} + +// MulWrap returns u*v with wraparound semantics; for example, +// Max.MulWrap(Max) == 1. +func (u Uint128) MulWrap(v Uint128) Uint128 { + hi, lo := bits.Mul64(u.Lo, v.Lo) + hi += u.Hi*v.Lo + u.Lo*v.Hi + return Uint128{lo, hi} +} + +// Mul64 returns u*v, panicking on overflow. +func (u Uint128) Mul64(v uint64) Uint128 { + hi, lo := bits.Mul64(u.Lo, v) + p0, p1 := bits.Mul64(u.Hi, v) + hi, c0 := bits.Add64(hi, p1, 0) + if p0 != 0 || c0 != 0 { + panic("overflow") + } + return Uint128{lo, hi} +} + +// MulWrap64 returns u*v with wraparound semantics; for example, +// Max.MulWrap64(2) == Max.Sub64(1). +func (u Uint128) MulWrap64(v uint64) Uint128 { + hi, lo := bits.Mul64(u.Lo, v) + hi += u.Hi * v + return Uint128{lo, hi} +} + +// Div returns u/v. +func (u Uint128) Div(v Uint128) Uint128 { + q, _ := u.QuoRem(v) + return q +} + +// Div64 returns u/v. +func (u Uint128) Div64(v uint64) Uint128 { + q, _ := u.QuoRem64(v) + return q +} + +// QuoRem returns q = u/v and r = u%v. +func (u Uint128) QuoRem(v Uint128) (q, r Uint128) { + if v.Hi == 0 { + var r64 uint64 + q, r64 = u.QuoRem64(v.Lo) + r = From64(r64) + } else { + // generate a "trial quotient," guaranteed to be within 1 of the actual + // quotient, then adjust. + n := uint(bits.LeadingZeros64(v.Hi)) + v1 := v.Lsh(n) + u1 := u.Rsh(1) + tq, _ := bits.Div64(u1.Hi, u1.Lo, v1.Hi) + tq >>= 63 - n + if tq != 0 { + tq-- + } + q = From64(tq) + // calculate remainder using trial quotient, then adjust if remainder is + // greater than divisor + r = u.Sub(v.Mul64(tq)) + if r.Cmp(v) >= 0 { + q = q.Add64(1) + r = r.Sub(v) + } + } + return +} + +// QuoRem64 returns q = u/v and r = u%v. +func (u Uint128) QuoRem64(v uint64) (q Uint128, r uint64) { + if u.Hi < v { + q.Lo, r = bits.Div64(u.Hi, u.Lo, v) + } else { + q.Hi, r = bits.Div64(0, u.Hi, v) + q.Lo, r = bits.Div64(r, u.Lo, v) + } + return +} + +// Mod returns r = u%v. +func (u Uint128) Mod(v Uint128) (r Uint128) { + _, r = u.QuoRem(v) + return +} + +// Mod64 returns r = u%v. +func (u Uint128) Mod64(v uint64) (r uint64) { + _, r = u.QuoRem64(v) + return +} + +// Lsh returns u< 64 { + s.Lo = 0 + s.Hi = u.Lo << (n - 64) + } else { + s.Lo = u.Lo << n + s.Hi = u.Hi<>(64-n) + } + return +} + +// Rsh returns u>>n. +func (u Uint128) Rsh(n uint) (s Uint128) { + if n > 64 { + s.Lo = u.Hi >> (n - 64) + s.Hi = 0 + } else { + s.Lo = u.Lo>>n | u.Hi<<(64-n) + s.Hi = u.Hi >> n + } + return +} + +// LeadingZeros returns the number of leading zero bits in u; the result is 128 +// for u == 0. +func (u Uint128) LeadingZeros() int { + if u.Hi > 0 { + return bits.LeadingZeros64(u.Hi) + } + return 64 + bits.LeadingZeros64(u.Lo) +} + +// TrailingZeros returns the number of trailing zero bits in u; the result is +// 128 for u == 0. +func (u Uint128) TrailingZeros() int { + if u.Lo > 0 { + return bits.TrailingZeros64(u.Lo) + } + return 64 + bits.TrailingZeros64(u.Hi) +} + +// OnesCount returns the number of one bits ("population count") in u. +func (u Uint128) OnesCount() int { + return bits.OnesCount64(u.Hi) + bits.OnesCount64(u.Lo) +} + +// RotateLeft returns the value of u rotated left by (k mod 128) bits. +func (u Uint128) RotateLeft(k int) Uint128 { + const n = 128 + s := uint(k) & (n - 1) + return u.Lsh(s).Or(u.Rsh(n - s)) +} + +// RotateRight returns the value of u rotated left by (k mod 128) bits. +func (u Uint128) RotateRight(k int) Uint128 { + return u.RotateLeft(-k) +} + +// Reverse returns the value of u with its bits in reversed order. +func (u Uint128) Reverse() Uint128 { + return Uint128{bits.Reverse64(u.Hi), bits.Reverse64(u.Lo)} +} + +// ReverseBytes returns the value of u with its bytes in reversed order. +func (u Uint128) ReverseBytes() Uint128 { + return Uint128{bits.ReverseBytes64(u.Hi), bits.ReverseBytes64(u.Lo)} +} + +// Len returns the minimum number of bits required to represent u; the result is +// 0 for u == 0. +func (u Uint128) Len() int { + return 128 - u.LeadingZeros() +} + +// String returns the base-10 representation of u as a string. +func (u Uint128) String() string { + if u.IsZero() { + return "0" + } + buf := []byte("0000000000000000000000000000000000000000") // log10(2^128) < 40 + for i := len(buf); ; i -= 19 { + q, r := u.QuoRem64(1e19) // largest power of 10 that fits in a uint64 + var n int + for ; r != 0; r /= 10 { + n++ + buf[i-n] += byte(r % 10) + } + if q.IsZero() { + return string(buf[i-n:]) + } + u = q + } +} + +// PutBytes stores u in b in little-endian order. It panics if len(b) < 16. +func (u Uint128) PutBytes(b []byte) { + binary.LittleEndian.PutUint64(b[:8], u.Lo) + binary.LittleEndian.PutUint64(b[8:], u.Hi) +} + +// Big returns u as a *big.Int. +func (u Uint128) Big() *big.Int { + i := new(big.Int).SetUint64(u.Hi) + i = i.Lsh(i, 64) + i = i.Xor(i, new(big.Int).SetUint64(u.Lo)) + return i +} + +// Scan implements fmt.Scanner. +func (u *Uint128) Scan(s fmt.ScanState, ch rune) error { + i := new(big.Int) + if err := i.Scan(s, ch); err != nil { + return err + } else if i.Sign() < 0 { + return errors.New("value cannot be negative") + } else if i.BitLen() > 128 { + return errors.New("value overflows Uint128") + } + u.Lo = i.Uint64() + u.Hi = i.Rsh(i, 64).Uint64() + return nil +} + +// New returns the Uint128 value (lo,hi). +func New(lo, hi uint64) Uint128 { + return Uint128{lo, hi} +} + +// From64 converts v to a Uint128 value. +func From64(v uint64) Uint128 { + return New(v, 0) +} + +// FromBytes converts b to a Uint128 value. +func FromBytes(b []byte) Uint128 { + return New( + binary.LittleEndian.Uint64(b[:8]), + binary.LittleEndian.Uint64(b[8:]), + ) +} + +// FromBig converts i to a Uint128 value. It panics if i is negative or +// overflows 128 bits. +func FromBig(i *big.Int) (u Uint128) { + if i.Sign() < 0 { + panic("value cannot be negative") + } else if i.BitLen() > 128 { + panic("value overflows Uint128") + } + u.Lo = i.Uint64() + u.Hi = i.Rsh(i, 64).Uint64() + return u +} + +// FromString parses s as a Uint128 value. +func FromString(s string) (u Uint128, err error) { + _, err = fmt.Sscan(s, &u) + return +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 6b3670bb2..c3237600c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -119,7 +119,7 @@ github.com/imdario/mergo # github.com/inconshreveable/mousetrap v1.1.0 ## explicit; go 1.18 github.com/inconshreveable/mousetrap -# github.com/ip2location/ip2location-go/v9 v9.2.0 +# github.com/ip2location/ip2location-go/v9 v9.6.0 ## explicit; go 1.14 github.com/ip2location/ip2location-go/v9 # github.com/josharian/intern v1.0.0 @@ -989,6 +989,9 @@ k8s.io/utils/net k8s.io/utils/pointer k8s.io/utils/strings/slices k8s.io/utils/trace +# lukechampine.com/uint128 v1.2.0 +## explicit; go 1.12 +lukechampine.com/uint128 # sigs.k8s.io/controller-runtime v0.15.1 ## explicit; go 1.20 sigs.k8s.io/controller-runtime/pkg/client