mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-22 08:31:30 +00:00
Add geosite protocol
This commit is contained in:
parent
43cf0441db
commit
f76102dab5
97
common/geosite/reader.go
Normal file
97
common/geosite/reader.go
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
package geosite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Reader struct {
|
||||||
|
reader io.ReadSeeker
|
||||||
|
access sync.Mutex
|
||||||
|
metadataRead bool
|
||||||
|
domainIndex map[string]int
|
||||||
|
domainLength map[string]int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readMetadata() error {
|
||||||
|
version, err := rw.ReadByte(r.reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if version != 0 {
|
||||||
|
return E.New("unknown version")
|
||||||
|
}
|
||||||
|
entryLength, err := rw.ReadUVariant(r.reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
keys := make([]string, entryLength)
|
||||||
|
domainIndex := make(map[string]int)
|
||||||
|
domainLength := make(map[string]int)
|
||||||
|
for i := 0; i < int(entryLength); i++ {
|
||||||
|
var (
|
||||||
|
code string
|
||||||
|
codeIndex uint64
|
||||||
|
codeLength uint64
|
||||||
|
)
|
||||||
|
code, err = rw.ReadVString(r.reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
keys[i] = code
|
||||||
|
codeIndex, err = rw.ReadUVariant(r.reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
codeLength, err = rw.ReadUVariant(r.reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
domainIndex[code] = int(codeIndex)
|
||||||
|
domainLength[code] = int(codeLength)
|
||||||
|
}
|
||||||
|
r.domainIndex = domainIndex
|
||||||
|
r.domainLength = domainLength
|
||||||
|
r.metadataRead = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) Read(code string) ([]Item, error) {
|
||||||
|
r.access.Lock()
|
||||||
|
defer r.access.Unlock()
|
||||||
|
if !r.metadataRead {
|
||||||
|
err := r.readMetadata()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, exists := r.domainIndex[code]; !exists {
|
||||||
|
return nil, E.New("code ", code, " not exists!")
|
||||||
|
}
|
||||||
|
counter := &rw.ReadCounter{Reader: r.reader}
|
||||||
|
domain := make([]Item, r.domainLength[code])
|
||||||
|
for i := range domain {
|
||||||
|
var (
|
||||||
|
item Item
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
item.Type, err = rw.ReadByte(counter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
item.Value, err = rw.ReadVString(counter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
domain[i] = item
|
||||||
|
}
|
||||||
|
_, err := r.reader.Seek(int64(r.domainIndex[code])-counter.Count(), io.SeekCurrent)
|
||||||
|
return domain, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) Upstream() any {
|
||||||
|
return r.reader
|
||||||
|
}
|
62
common/geosite/rule.go
Normal file
62
common/geosite/rule.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package geosite
|
||||||
|
|
||||||
|
import "github.com/sagernet/sing-box/option"
|
||||||
|
|
||||||
|
type ItemType = uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
RuleTypeDomain ItemType = iota
|
||||||
|
RuleTypeDomainSuffix
|
||||||
|
RuleTypeDomainKeyword
|
||||||
|
RuleTypeDomainRegex
|
||||||
|
)
|
||||||
|
|
||||||
|
type Item struct {
|
||||||
|
Type ItemType
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Compile(code []Item) option.DefaultRule {
|
||||||
|
var domainLength int
|
||||||
|
var domainSuffixLength int
|
||||||
|
var domainKeywordLength int
|
||||||
|
var domainRegexLength int
|
||||||
|
for _, item := range code {
|
||||||
|
switch item.Type {
|
||||||
|
case RuleTypeDomain:
|
||||||
|
domainLength++
|
||||||
|
case RuleTypeDomainSuffix:
|
||||||
|
domainSuffixLength++
|
||||||
|
case RuleTypeDomainKeyword:
|
||||||
|
domainKeywordLength++
|
||||||
|
case RuleTypeDomainRegex:
|
||||||
|
domainRegexLength++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var codeRule option.DefaultRule
|
||||||
|
if domainLength > 0 {
|
||||||
|
codeRule.Domain = make([]string, 0, domainLength)
|
||||||
|
}
|
||||||
|
if domainSuffixLength > 0 {
|
||||||
|
codeRule.DomainSuffix = make([]string, 0, domainSuffixLength)
|
||||||
|
}
|
||||||
|
if domainKeywordLength > 0 {
|
||||||
|
codeRule.DomainKeyword = make([]string, 0, domainKeywordLength)
|
||||||
|
}
|
||||||
|
if domainRegexLength > 0 {
|
||||||
|
codeRule.DomainRegex = make([]string, 0, domainRegexLength)
|
||||||
|
}
|
||||||
|
for _, item := range code {
|
||||||
|
switch item.Type {
|
||||||
|
case RuleTypeDomain:
|
||||||
|
codeRule.Domain = append(codeRule.Domain, item.Value)
|
||||||
|
case RuleTypeDomainSuffix:
|
||||||
|
codeRule.DomainSuffix = append(codeRule.DomainSuffix, item.Value)
|
||||||
|
case RuleTypeDomainKeyword:
|
||||||
|
codeRule.DomainKeyword = append(codeRule.DomainKeyword, item.Value)
|
||||||
|
case RuleTypeDomainRegex:
|
||||||
|
codeRule.DomainRegex = append(codeRule.DomainRegex, item.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return codeRule
|
||||||
|
}
|
64
common/geosite/writer.go
Normal file
64
common/geosite/writer.go
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package geosite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Write(writer io.Writer, domains map[string][]Item) error {
|
||||||
|
keys := make([]string, 0, len(domains))
|
||||||
|
for code := range domains {
|
||||||
|
keys = append(keys, code)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
content := &bytes.Buffer{}
|
||||||
|
index := make(map[string]int)
|
||||||
|
for _, code := range keys {
|
||||||
|
index[code] = content.Len()
|
||||||
|
for _, domain := range domains[code] {
|
||||||
|
err := rw.WriteByte(content, byte(domain.Type))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = rw.WriteVString(content, domain.Value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := rw.WriteByte(writer, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rw.WriteUVariant(writer, uint64(len(keys)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, code := range keys {
|
||||||
|
err = rw.WriteVString(writer, code)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = rw.WriteUVariant(writer, uint64(index[code]))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = rw.WriteUVariant(writer, uint64(len(domains[code])))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = writer.Write(content.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in a new issue