From 70d24d2f15ab0c70cb7c1402fd5d782cebf4d2ab Mon Sep 17 00:00:00 2001 From: Muse Date: Tue, 20 Oct 2026 11:04:59 +0000 Subject: [PATCH] =?UTF-8?q?=E7=B2=BE=E7=AE=80P2:=20geodata=20=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E5=A4=B9=E5=85=B7=E6=94=B9=20protowire=20=E6=89=8B?= =?UTF-8?q?=E5=B7=A5=E7=BC=96=E7=A0=81(geodat.proto=20=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E5=8F=B7+proto3=20=E9=9B=B6=E5=80=BC=E7=9C=81=E7=95=A5?= =?UTF-8?q?=E8=AF=AD=E4=B9=89=E5=8E=9F=E6=A0=B7=E5=A4=8D=E5=88=BB),=20?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E4=BE=9D=E8=B5=96=20xray-core=20=E7=94=9F?= =?UTF-8?q?=E6=88=90=E7=B1=BB=E5=9E=8B=E5=86=99=20.dat=20=E5=A4=B9?= =?UTF-8?q?=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/web/controller/geodata_test.go | 65 +++++++---- internal/xray/geodata/geodata_test.go | 148 +++++++++++++++++------- 2 files changed, 148 insertions(+), 65 deletions(-) diff --git a/internal/web/controller/geodata_test.go b/internal/web/controller/geodata_test.go index a1aa7a4..787fa79 100644 --- a/internal/web/controller/geodata_test.go +++ b/internal/web/controller/geodata_test.go @@ -13,8 +13,7 @@ import ( "github.com/gin-gonic/gin" "github.com/op/go-logging" - xraygeodata "github.com/xtls/xray-core/common/geodata" - "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/encoding/protowire" xuilogger "github.com/mhsanaei/3x-ui/v3/internal/logger" "github.com/mhsanaei/3x-ui/v3/internal/web/service" @@ -36,20 +35,47 @@ func newGeodataEngine(t *testing.T) *gin.Engine { return engine } +// The fixture databases are encoded with protowire: the wire layout +// (geodat.proto field numbers) is the contract under test, and spelling it +// out keeps the test independent of xray-core's generated types. + +func geoWireBytes(num protowire.Number, payload []byte) []byte { + out := protowire.AppendTag(nil, num, protowire.BytesType) + return protowire.AppendBytes(out, payload) +} + +func geoWireString(num protowire.Number, value string) []byte { + out := protowire.AppendTag(nil, num, protowire.BytesType) + return protowire.AppendString(out, value) +} + +func geoWireVarint(num protowire.Number, value uint64) []byte { + out := protowire.AppendTag(nil, num, protowire.VarintType) + return protowire.AppendVarint(out, value) +} + +func geoFixtureDomain(typ uint64, value string, attrs ...string) []byte { + var msg []byte + if typ != 0 { + msg = append(msg, geoWireVarint(1, typ)...) + } + msg = append(msg, geoWireString(2, value)...) + for _, attr := range attrs { + attrMsg := geoWireString(1, attr) + attrMsg = append(attrMsg, geoWireVarint(2, 1)...) // bool_value = true + msg = append(msg, geoWireBytes(3, attrMsg)...) + } + return msg +} + func writeGeositeDB(t *testing.T, dir string) { t.Helper() - data, err := proto.Marshal(&xraygeodata.GeoSiteList{Entry: []*xraygeodata.GeoSite{ - {Code: "google", Domain: []*xraygeodata.Domain{ - {Type: xraygeodata.Domain_Domain, Value: "google.com"}, - {Type: xraygeodata.Domain_Full, Value: "ads.google.com", Attribute: []*xraygeodata.Domain_Attribute{ - {Key: "ads", TypedValue: &xraygeodata.Domain_Attribute_BoolValue{BoolValue: true}}, - }}, - }}, - {Code: "cn", Domain: []*xraygeodata.Domain{{Type: xraygeodata.Domain_Domain, Value: "baidu.com"}}}, - }}) - if err != nil { - t.Fatalf("marshal geosite: %v", err) - } + google := geoWireString(1, "google") + google = append(google, geoWireBytes(2, geoFixtureDomain(2, "google.com"))...) + google = append(google, geoWireBytes(2, geoFixtureDomain(3, "ads.google.com", "ads"))...) + cn := geoWireString(1, "cn") + cn = append(cn, geoWireBytes(2, geoFixtureDomain(2, "baidu.com"))...) + data := append(geoWireBytes(1, google), geoWireBytes(1, cn)...) if err := os.WriteFile(filepath.Join(dir, "geosite.dat"), data, 0o644); err != nil { t.Fatalf("write geosite.dat: %v", err) } @@ -58,12 +84,11 @@ func writeGeositeDB(t *testing.T, dir string) { func writeGeoipDB(t *testing.T, dir string) { t.Helper() prefix := netip.MustParsePrefix("10.0.0.0/8") - data, err := proto.Marshal(&xraygeodata.GeoIPList{Entry: []*xraygeodata.GeoIP{ - {Code: "private", Cidr: []*xraygeodata.CIDR{{Ip: prefix.Addr().AsSlice(), Prefix: uint32(prefix.Bits())}}}, - }}) - if err != nil { - t.Fatalf("marshal geoip: %v", err) - } + cidr := geoWireBytes(1, prefix.Addr().AsSlice()) + cidr = append(cidr, geoWireVarint(2, uint64(prefix.Bits()))...) + entry := geoWireString(1, "private") + entry = append(entry, geoWireBytes(2, cidr)...) + data := geoWireBytes(1, entry) if err := os.WriteFile(filepath.Join(dir, "geoip.dat"), data, 0o644); err != nil { t.Fatalf("write geoip.dat: %v", err) } diff --git a/internal/xray/geodata/geodata_test.go b/internal/xray/geodata/geodata_test.go index 00686a1..eb67e63 100644 --- a/internal/xray/geodata/geodata_test.go +++ b/internal/xray/geodata/geodata_test.go @@ -12,24 +12,97 @@ import ( "testing" "time" - xraygeodata "github.com/xtls/xray-core/common/geodata" - "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/encoding/protowire" ) -func writeSiteDB(t *testing.T, dir, name string, sites ...*xraygeodata.GeoSite) string { +// The fixtures are encoded with protowire directly: the databases' wire +// layout is the contract this package parses, and spelling it out keeps the +// tests independent of xray-core's generated types. Field numbers follow +// geodat.proto — list entry 1; entry code 1, payload 2; domain type 1, +// value 2, attribute 3; attribute key 1, bool value 2; cidr ip 1, prefix 2. +// proto3 zero-value omission is reproduced exactly (a domain of type 0 and +// a /0 prefix emit no field), because the reader's behavior depends on it. + +// Domain match types, mirroring xray's Domain_Type enum. +const ( + DomainSubstr = 0 + DomainRegex = 1 + DomainDomain = 2 + DomainFull = 3 +) + +type fixtureDomain struct { + typ uint64 + value string + attrs []string +} + +type fixtureSite struct { + code string + domains []fixtureDomain +} + +type fixtureIP struct { + code string + prefixes []string +} + +func wireBytes(num protowire.Number, payload []byte) []byte { + out := protowire.AppendTag(nil, num, protowire.BytesType) + return protowire.AppendBytes(out, payload) +} + +func wireString(num protowire.Number, value string) []byte { + out := protowire.AppendTag(nil, num, protowire.BytesType) + return protowire.AppendString(out, value) +} + +func wireVarint(num protowire.Number, value uint64) []byte { + out := protowire.AppendTag(nil, num, protowire.VarintType) + return protowire.AppendVarint(out, value) +} + +func encodeFixtureDomain(d fixtureDomain) []byte { + var msg []byte + if d.typ != 0 { + msg = append(msg, wireVarint(1, d.typ)...) + } + msg = append(msg, wireString(2, d.value)...) + for _, attr := range d.attrs { + attrMsg := wireString(1, attr) + attrMsg = append(attrMsg, wireVarint(2, 1)...) // bool_value = true + msg = append(msg, wireBytes(3, attrMsg)...) + } + return msg +} + +func writeSiteDB(t *testing.T, dir, name string, sites ...fixtureSite) string { t.Helper() - data, err := proto.Marshal(&xraygeodata.GeoSiteList{Entry: sites}) - if err != nil { - t.Fatalf("marshal geosite list: %v", err) + var data []byte + for _, s := range sites { + entry := wireString(1, s.code) + for _, d := range s.domains { + entry = append(entry, wireBytes(2, encodeFixtureDomain(d))...) + } + data = append(data, wireBytes(1, entry)...) } return writeFile(t, dir, name, data) } -func writeIPDB(t *testing.T, dir, name string, geoips ...*xraygeodata.GeoIP) string { +func writeIPDB(t *testing.T, dir, name string, geoips ...fixtureIP) string { t.Helper() - data, err := proto.Marshal(&xraygeodata.GeoIPList{Entry: geoips}) - if err != nil { - t.Fatalf("marshal geoip list: %v", err) + var data []byte + for _, g := range geoips { + entry := wireString(1, g.code) + for _, raw := range g.prefixes { + prefix := netip.MustParsePrefix(raw) + cidr := wireBytes(1, prefix.Addr().AsSlice()) + if prefix.Bits() != 0 { + cidr = append(cidr, wireVarint(2, uint64(prefix.Bits()))...) + } + entry = append(entry, wireBytes(2, cidr)...) + } + data = append(data, wireBytes(1, entry)...) } return writeFile(t, dir, name, data) } @@ -43,45 +116,30 @@ func writeFile(t *testing.T, dir, name string, data []byte) string { return path } -func site(code string, domains ...*xraygeodata.Domain) *xraygeodata.GeoSite { - return &xraygeodata.GeoSite{Code: code, Domain: domains} +func site(code string, domains ...fixtureDomain) fixtureSite { + return fixtureSite{code: code, domains: domains} } -func domain(domainType xraygeodata.Domain_Type, value string, attributes ...string) *xraygeodata.Domain { - d := &xraygeodata.Domain{Type: domainType, Value: value} - for _, attribute := range attributes { - d.Attribute = append(d.Attribute, &xraygeodata.Domain_Attribute{ - Key: attribute, - TypedValue: &xraygeodata.Domain_Attribute_BoolValue{BoolValue: true}, - }) - } - return d +func domain(domainType uint64, value string, attributes ...string) fixtureDomain { + return fixtureDomain{typ: domainType, value: value, attrs: attributes} } -func geoip(code string, prefixes ...string) *xraygeodata.GeoIP { - entry := &xraygeodata.GeoIP{Code: code} - for _, raw := range prefixes { - prefix := netip.MustParsePrefix(raw) - entry.Cidr = append(entry.Cidr, &xraygeodata.CIDR{ - Ip: prefix.Addr().AsSlice(), - Prefix: uint32(prefix.Bits()), - }) - } - return entry +func geoip(code string, prefixes ...string) fixtureIP { + return fixtureIP{code: code, prefixes: prefixes} } func sampleSiteDB(t *testing.T, dir string) { t.Helper() writeSiteDB(t, dir, "geosite.dat", site("google", - domain(xraygeodata.Domain_Domain, "google.com"), - domain(xraygeodata.Domain_Full, "ads.google.com", "ads"), - domain(xraygeodata.Domain_Substr, "googlevideo", "cn"), - domain(xraygeodata.Domain_Regex, `^g.*\.cn$`), + domain(DomainDomain, "google.com"), + domain(DomainFull, "ads.google.com", "ads"), + domain(DomainSubstr, "googlevideo", "cn"), + domain(DomainRegex, `^g.*\.cn$`), ), site("CN", - domain(xraygeodata.Domain_Domain, "baidu.com"), - domain(xraygeodata.Domain_Domain, "qq.com"), + domain(DomainDomain, "baidu.com"), + domain(DomainDomain, "qq.com"), ), ) } @@ -126,7 +184,7 @@ func TestListFilesReportsKindAndCategories(t *testing.T) { func TestKindDetectedFromContentsNotName(t *testing.T) { dir := t.TempDir() - writeSiteDB(t, dir, "my_ip_rules.dat", site("corp", domain(xraygeodata.Domain_Domain, "intranet.corp.local"))) + writeSiteDB(t, dir, "my_ip_rules.dat", site("corp", domain(DomainDomain, "intranet.corp.local"))) writeIPDB(t, dir, "custom_sites.dat", geoip("office", "192.168.7.0/24")) store := NewStore(dir) @@ -241,9 +299,9 @@ func TestSearchFilters(t *testing.T) { func TestPagination(t *testing.T) { dir := t.TempDir() - domains := make([]*xraygeodata.Domain, 0, 250) + domains := make([]fixtureDomain, 0, 250) for i := range 250 { - domains = append(domains, domain(xraygeodata.Domain_Domain, "host"+strconv.Itoa(i)+".example.com")) + domains = append(domains, domain(DomainDomain, "host"+strconv.Itoa(i)+".example.com")) } writeSiteDB(t, dir, "geosite.dat", site("bulk", domains...)) store := NewStore(dir) @@ -284,9 +342,9 @@ func TestPagination(t *testing.T) { func TestCategoriesReturnEverythingWithoutLimit(t *testing.T) { dir := t.TempDir() - sites := make([]*xraygeodata.GeoSite, 0, MaxPageSize+20) + sites := make([]fixtureSite, 0, MaxPageSize+20) for i := range MaxPageSize + 20 { - sites = append(sites, site("cat"+strconv.Itoa(i), domain(xraygeodata.Domain_Domain, "example.com"))) + sites = append(sites, site("cat"+strconv.Itoa(i), domain(DomainDomain, "example.com"))) } writeSiteDB(t, dir, "geosite.dat", sites...) store := NewStore(dir) @@ -421,9 +479,9 @@ func TestIndexCacheInvalidatedWhenFileChanges(t *testing.T) { } path := writeSiteDB(t, dir, "geosite.dat", - site("google", domain(xraygeodata.Domain_Domain, "google.com")), - site("cn", domain(xraygeodata.Domain_Domain, "baidu.com")), - site("telegram", domain(xraygeodata.Domain_Domain, "t.me")), + site("google", domain(DomainDomain, "google.com")), + site("cn", domain(DomainDomain, "baidu.com")), + site("telegram", domain(DomainDomain, "t.me")), ) touch(t, path, time.Now().Add(time.Second))