decode.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  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. "os"
  13. "runtime"
  14. "strings"
  15. "google.golang.org/protobuf/encoding/protowire"
  16. "github.com/v2fly/v2ray-core/v4/common/errors"
  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. )
  25. func emitBytes(f *os.File, code string) ([]byte, error) {
  26. count := 1
  27. isInner := false
  28. tempContainer := make([]byte, 0, 5)
  29. var result []byte
  30. var advancedN uint64 = 1
  31. var geoDataVarintLength, codeVarintLength, varintLenByteLen uint64 = 0, 0, 0
  32. Loop:
  33. for {
  34. container := make([]byte, advancedN)
  35. bytesRead, err := f.Read(container)
  36. if err != nil {
  37. return nil, errFailedToReadBytes
  38. }
  39. if bytesRead != len(container) {
  40. return nil, errFailedToReadExpectedLenBytes
  41. }
  42. switch count {
  43. case 1, 3: // data type ((field_number << 3) | wire_type)
  44. if container[0] != 10 { // byte `0A` equals to `10` in decimal
  45. return nil, errInvalidGeodataFile
  46. }
  47. advancedN = 1
  48. count++
  49. case 2, 4: // data length
  50. tempContainer = append(tempContainer, container...)
  51. if container[0] > 127 { // max one-byte-length byte `7F`(0FFF FFFF) equals to `127` in decimal
  52. advancedN = 1
  53. goto Loop
  54. }
  55. lenVarint, n := protowire.ConsumeVarint(tempContainer)
  56. if n < 0 {
  57. return nil, errInvalidGeodataVarintLength
  58. }
  59. tempContainer = nil
  60. if !isInner {
  61. isInner = true
  62. geoDataVarintLength = lenVarint
  63. advancedN = 1
  64. } else {
  65. isInner = false
  66. codeVarintLength = lenVarint
  67. varintLenByteLen = uint64(n)
  68. advancedN = codeVarintLength
  69. }
  70. count++
  71. case 5: // data value
  72. if strings.EqualFold(string(container), code) {
  73. count++
  74. offset := -(1 + int64(varintLenByteLen) + int64(codeVarintLength))
  75. f.Seek(offset, 1) // back to the start of GeoIP or GeoSite varint
  76. advancedN = geoDataVarintLength // the number of bytes to be read in next round
  77. } else {
  78. count = 1
  79. offset := int64(geoDataVarintLength) - int64(codeVarintLength) - int64(varintLenByteLen) - 1
  80. f.Seek(offset, 1) // skip the unmatched GeoIP or GeoSite varint
  81. advancedN = 1 // the next round will be the start of another GeoIPList or GeoSiteList
  82. }
  83. case 6: // matched GeoIP or GeoSite varint
  84. result = container
  85. break Loop
  86. }
  87. runtime.GC() // run GC every round to save memory
  88. }
  89. runtime.GC() // run GC at the end to save memory
  90. return result, nil
  91. }
  92. func Decode(filename, code string) ([]byte, error) {
  93. f, err := os.Open(filename)
  94. if err != nil {
  95. return nil, newError("failed to open file: ", filename).Base(err)
  96. }
  97. defer f.Close()
  98. geoBytes, err := emitBytes(f, code)
  99. if err != nil {
  100. return nil, err
  101. }
  102. return geoBytes, nil
  103. }