github.com/kelleygo/clashcore@v1.0.2/component/geodata/memconservative/decode.go (about) 1 package memconservative 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "os" 8 "strings" 9 10 "google.golang.org/protobuf/encoding/protowire" 11 ) 12 13 var ( 14 errFailedToReadBytes = errors.New("failed to read bytes") 15 errFailedToReadExpectedLenBytes = errors.New("failed to read expected length of bytes") 16 errInvalidGeodataFile = errors.New("invalid geodata file") 17 errInvalidGeodataVarintLength = errors.New("invalid geodata varint length") 18 errCodeNotFound = errors.New("code not found") 19 ) 20 21 func emitBytes(f io.ReadSeeker, code string) ([]byte, error) { 22 count := 1 23 isInner := false 24 tempContainer := make([]byte, 0, 5) 25 26 var result []byte 27 var advancedN uint64 = 1 28 var geoDataVarintLength, codeVarintLength, varintLenByteLen uint64 = 0, 0, 0 29 30 Loop: 31 for { 32 container := make([]byte, advancedN) 33 bytesRead, err := f.Read(container) 34 if err == io.EOF { 35 return nil, errCodeNotFound 36 } 37 if err != nil { 38 return nil, errFailedToReadBytes 39 } 40 if bytesRead != len(container) { 41 return nil, errFailedToReadExpectedLenBytes 42 } 43 44 switch count { 45 case 1, 3: // data type ((field_number << 3) | wire_type) 46 if container[0] != 10 { // byte `0A` equals to `10` in decimal 47 return nil, errInvalidGeodataFile 48 } 49 advancedN = 1 50 count++ 51 case 2, 4: // data length 52 tempContainer = append(tempContainer, container...) 53 if container[0] > 127 { // max one-byte-length byte `7F`(0FFF FFFF) equals to `127` in decimal 54 advancedN = 1 55 goto Loop 56 } 57 lenVarint, n := protowire.ConsumeVarint(tempContainer) 58 if n < 0 { 59 return nil, errInvalidGeodataVarintLength 60 } 61 tempContainer = nil 62 if !isInner { 63 isInner = true 64 geoDataVarintLength = lenVarint 65 advancedN = 1 66 } else { 67 isInner = false 68 codeVarintLength = lenVarint 69 varintLenByteLen = uint64(n) 70 advancedN = codeVarintLength 71 } 72 count++ 73 case 5: // data value 74 if strings.EqualFold(string(container), code) { 75 count++ 76 offset := -(1 + int64(varintLenByteLen) + int64(codeVarintLength)) 77 _, _ = f.Seek(offset, 1) // back to the start of GeoIP or GeoSite varint 78 advancedN = geoDataVarintLength // the number of bytes to be read in next round 79 } else { 80 count = 1 81 offset := int64(geoDataVarintLength) - int64(codeVarintLength) - int64(varintLenByteLen) - 1 82 _, _ = f.Seek(offset, 1) // skip the unmatched GeoIP or GeoSite varint 83 advancedN = 1 // the next round will be the start of another GeoIPList or GeoSiteList 84 } 85 case 6: // matched GeoIP or GeoSite varint 86 result = container 87 break Loop 88 } 89 } 90 return result, nil 91 } 92 93 func Decode(filename, code string) ([]byte, error) { 94 f, err := os.Open(filename) 95 if err != nil { 96 return nil, fmt.Errorf("failed to open file: %s, base error: %s", filename, err.Error()) 97 } 98 defer func(f *os.File) { 99 _ = f.Close() 100 }(f) 101 102 geoBytes, err := emitBytes(f, code) 103 if err != nil { 104 return nil, err 105 } 106 return geoBytes, nil 107 }