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  }