decode.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. // Package geodata includes utilities to decode and parse the geoip & geosite dat files for V2Ray.
  2. //
  3. // It relies on the proto structure of GeoIP, GeoIPList, GeoSite and GeoSiteList in
  4. // github.com/v2fly/v2ray-core/v4/app/router/config.proto to comply with following rules:
  5. //
  6. // 1. GeoIPList and GeoSiteList cannot be changed
  7. // 2. The country_code in GeoIP and GeoSite must be
  8. // a length-delimited `string`(wired type) and has field_number set to 1
  9. //
  10. package geodata
  11. import (
  12. "io"
  13. "strings"
  14. "google.golang.org/protobuf/encoding/protowire"
  15. "github.com/v2fly/v2ray-core/v4/common/errors"
  16. "github.com/v2fly/v2ray-core/v4/common/platform/filesystem"
  17. )
  18. //go:generate go run github.com/v2fly/v2ray-core/v4/common/errors/errorgen
  19. var (
  20. ErrFailedToReadBytes = errors.New("failed to read bytes")
  21. ErrFailedToReadExpectedLenBytes = errors.New("failed to read expected length of bytes")
  22. ErrInvalidGeodataFile = errors.New("invalid geodata file")
  23. ErrInvalidGeodataVarintLength = errors.New("invalid geodata varint length")
  24. ErrCodeNotFound = errors.New("code not found")
  25. )
  26. func EmitBytes(f io.ReadSeeker, code string) ([]byte, error) {
  27. count := 1
  28. isInner := false
  29. tempContainer := make([]byte, 0, 5)
  30. var result []byte
  31. var advancedN uint64 = 1
  32. var geoDataVarintLength, codeVarintLength, varintLenByteLen uint64 = 0, 0, 0
  33. Loop:
  34. for {
  35. container := make([]byte, advancedN)
  36. bytesRead, err := f.Read(container)
  37. if err == io.EOF {
  38. return nil, ErrCodeNotFound
  39. }
  40. if err != nil {
  41. return nil, ErrFailedToReadBytes
  42. }
  43. if bytesRead != len(container) {
  44. return nil, ErrFailedToReadExpectedLenBytes
  45. }
  46. switch count {
  47. case 1, 3: // data type ((field_number << 3) | wire_type)
  48. if container[0] != 10 { // byte `0A` equals to `10` in decimal
  49. return nil, ErrInvalidGeodataFile
  50. }
  51. advancedN = 1
  52. count++
  53. case 2, 4: // data length
  54. tempContainer = append(tempContainer, container...)
  55. if container[0] > 127 { // max one-byte-length byte `7F`(0FFF FFFF) equals to `127` in decimal
  56. advancedN = 1
  57. goto Loop
  58. }
  59. lenVarint, n := protowire.ConsumeVarint(tempContainer)
  60. if n < 0 {
  61. return nil, ErrInvalidGeodataVarintLength
  62. }
  63. tempContainer = nil
  64. if !isInner {
  65. isInner = true
  66. geoDataVarintLength = lenVarint
  67. advancedN = 1
  68. } else {
  69. isInner = false
  70. codeVarintLength = lenVarint
  71. varintLenByteLen = uint64(n)
  72. advancedN = codeVarintLength
  73. }
  74. count++
  75. case 5: // data value
  76. if strings.EqualFold(string(container), code) {
  77. count++
  78. offset := -(1 + int64(varintLenByteLen) + int64(codeVarintLength))
  79. f.Seek(offset, 1) // back to the start of GeoIP or GeoSite varint
  80. advancedN = geoDataVarintLength // the number of bytes to be read in next round
  81. } else {
  82. count = 1
  83. offset := int64(geoDataVarintLength) - int64(codeVarintLength) - int64(varintLenByteLen) - 1
  84. f.Seek(offset, 1) // skip the unmatched GeoIP or GeoSite varint
  85. advancedN = 1 // the next round will be the start of another GeoIPList or GeoSiteList
  86. }
  87. case 6: // matched GeoIP or GeoSite varint
  88. result = container
  89. break Loop
  90. }
  91. }
  92. return result, nil
  93. }
  94. func Decode(filename, code string) ([]byte, error) {
  95. f, err := filesystem.NewFileSeeker(filename)
  96. if err != nil {
  97. return nil, newError("failed to open file: ", filename).Base(err)
  98. }
  99. defer f.Close()
  100. geoBytes, err := EmitBytes(f, code)
  101. if err != nil {
  102. return nil, err
  103. }
  104. return geoBytes, nil
  105. }