pax_global_header00006660000000000000000000000064137175215610014522gustar00rootroot0000000000000052 comment=52f6238399c05ce5bdd610d276cefb66b4eae256 maxminddb-golang-1.8.0/000077500000000000000000000000001371752156100147405ustar00rootroot00000000000000maxminddb-golang-1.8.0/.github/000077500000000000000000000000001371752156100163005ustar00rootroot00000000000000maxminddb-golang-1.8.0/.github/workflows/000077500000000000000000000000001371752156100203355ustar00rootroot00000000000000maxminddb-golang-1.8.0/.github/workflows/codeql-analysis.yml000066400000000000000000000030471371752156100241540ustar00rootroot00000000000000name: "Code scanning - action" on: push: pull_request: schedule: - cron: '0 13 * * 4' jobs: CodeQL-Build: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v2 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. fetch-depth: 2 # If this run was triggered by a pull request event, then checkout # the head of the pull request instead of the merge commit. - run: git checkout HEAD^2 if: ${{ github.event_name == 'pull_request' }} # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v1 # Override language selection by uncommenting this and choosing your languages # with: # languages: go, javascript, csharp, python, cpp, java # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v1 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 maxminddb-golang-1.8.0/.github/workflows/go.yml000066400000000000000000000012671371752156100214730ustar00rootroot00000000000000name: Go on: [push, pull_request] jobs: build: name: Build strategy: matrix: go-version: [1.13.x, 1.14.x, 1.15.x] platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} steps: - name: Set up Go 1.x uses: actions/setup-go@v2 with: go-version: ${{ matrix.go-version }} id: go - name: Check out code into the Go module directory uses: actions/checkout@v2 with: submodules: true - name: Get dependencies run: go get -v -t -d ./... - name: Build run: go build -v . - name: Test run: go test -race -v ./... maxminddb-golang-1.8.0/.github/workflows/golangci-lint.yml000066400000000000000000000004121371752156100236040ustar00rootroot00000000000000name: golangci-lint on: [push, pull_request] jobs: golangci: name: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: golangci-lint uses: golangci/golangci-lint-action@v1 with: version: v1.30 maxminddb-golang-1.8.0/.gitignore000066400000000000000000000000331371752156100167240ustar00rootroot00000000000000.vscode *.out *.sw? *.test maxminddb-golang-1.8.0/.gitmodules000066400000000000000000000001331371752156100171120ustar00rootroot00000000000000[submodule "test-data"] path = test-data url = https://github.com/maxmind/MaxMind-DB.git maxminddb-golang-1.8.0/.golangci.toml000066400000000000000000000017101371752156100174750ustar00rootroot00000000000000[run] deadline = "10m" tests = true [linters] disable-all = true enable = [ "bodyclose", "deadcode", "depguard", "errcheck", "exportloopref", "goconst", "gocyclo", "gocritic", "gofumpt", "golint", "gosec", "gosimple", "govet", "ineffassign", "maligned", "misspell", "nakedret", "noctx", "nolintlint", "sqlclosecheck", "staticcheck", "structcheck", "stylecheck", "typecheck", "unconvert", "unparam", "unused", "varcheck", "vetshadow", ] [linters-settings.errcheck] ignore = "Close,fmt:.*" [linters-settings.gofumpt] extra-rules = true [issues] exclude-use-default = false [[issues.exclude-rules]] linters = [ "gosec" ] # G304 - Potential file inclusion via variable (gosec) # G404 - "Use of weak random number generator (math/rand instead of crypto/rand)" # We only use this in tests. text = "G304|G404" maxminddb-golang-1.8.0/LICENSE000066400000000000000000000014041371752156100157440ustar00rootroot00000000000000ISC License Copyright (c) 2015, Gregory J. Oschwald Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. maxminddb-golang-1.8.0/README.md000066400000000000000000000017431371752156100162240ustar00rootroot00000000000000# MaxMind DB Reader for Go # [![GoDoc](https://godoc.org/github.com/oschwald/maxminddb-golang?status.svg)](https://godoc.org/github.com/oschwald/maxminddb-golang) This is a Go reader for the MaxMind DB format. Although this can be used to read [GeoLite2](http://dev.maxmind.com/geoip/geoip2/geolite2/) and [GeoIP2](https://www.maxmind.com/en/geoip2-databases) databases, [geoip2](https://github.com/oschwald/geoip2-golang) provides a higher-level API for doing so. This is not an official MaxMind API. ## Installation ## ``` go get github.com/oschwald/maxminddb-golang ``` ## Usage ## [See GoDoc](http://godoc.org/github.com/oschwald/maxminddb-golang) for documentation and examples. ## Examples ## See [GoDoc](http://godoc.org/github.com/oschwald/maxminddb-golang) or `example_test.go` for examples. ## Contributing ## Contributions welcome! Please fork the repository and open a pull request with your changes. ## License ## This is free software, licensed under the ISC License. maxminddb-golang-1.8.0/decoder.go000066400000000000000000000507471371752156100167110ustar00rootroot00000000000000package maxminddb import ( "encoding/binary" "math" "math/big" "reflect" "sync" ) type decoder struct { buffer []byte } type dataType int const ( _Extended dataType = iota _Pointer _String _Float64 _Bytes _Uint16 _Uint32 _Map _Int32 _Uint64 _Uint128 _Slice // We don't use the next two. They are placeholders. See the spec // for more details. _Container // nolint: deadcode, varcheck _Marker // nolint: deadcode, varcheck _Bool _Float32 ) const ( // This is the value used in libmaxminddb maximumDataStructureDepth = 512 ) func (d *decoder) decode(offset uint, result reflect.Value, depth int) (uint, error) { if depth > maximumDataStructureDepth { return 0, newInvalidDatabaseError("exceeded maximum data structure depth; database is likely corrupt") } typeNum, size, newOffset, err := d.decodeCtrlData(offset) if err != nil { return 0, err } if typeNum != _Pointer && result.Kind() == reflect.Uintptr { result.Set(reflect.ValueOf(uintptr(offset))) return d.nextValueOffset(offset, 1) } return d.decodeFromType(typeNum, size, newOffset, result, depth+1) } func (d *decoder) decodeToDeserializer(offset uint, dser deserializer, depth int) (uint, error) { if depth > maximumDataStructureDepth { return 0, newInvalidDatabaseError("exceeded maximum data structure depth; database is likely corrupt") } typeNum, size, newOffset, err := d.decodeCtrlData(offset) if err != nil { return 0, err } skip, err := dser.ShouldSkip(uintptr(offset)) if err != nil { return 0, err } if skip { return d.nextValueOffset(offset, 1) } return d.decodeFromTypeToDeserializer(typeNum, size, newOffset, dser, depth+1) } func (d *decoder) decodeCtrlData(offset uint) (dataType, uint, uint, error) { newOffset := offset + 1 if offset >= uint(len(d.buffer)) { return 0, 0, 0, newOffsetError() } ctrlByte := d.buffer[offset] typeNum := dataType(ctrlByte >> 5) if typeNum == _Extended { if newOffset >= uint(len(d.buffer)) { return 0, 0, 0, newOffsetError() } typeNum = dataType(d.buffer[newOffset] + 7) newOffset++ } var size uint size, newOffset, err := d.sizeFromCtrlByte(ctrlByte, newOffset, typeNum) return typeNum, size, newOffset, err } func (d *decoder) sizeFromCtrlByte(ctrlByte byte, offset uint, typeNum dataType) (uint, uint, error) { size := uint(ctrlByte & 0x1f) if typeNum == _Extended { return size, offset, nil } var bytesToRead uint if size < 29 { return size, offset, nil } bytesToRead = size - 28 newOffset := offset + bytesToRead if newOffset > uint(len(d.buffer)) { return 0, 0, newOffsetError() } if size == 29 { return 29 + uint(d.buffer[offset]), offset + 1, nil } sizeBytes := d.buffer[offset:newOffset] switch { case size == 30: size = 285 + uintFromBytes(0, sizeBytes) case size > 30: size = uintFromBytes(0, sizeBytes) + 65821 } return size, newOffset, nil } func (d *decoder) decodeFromType( dtype dataType, size uint, offset uint, result reflect.Value, depth int, ) (uint, error) { result = d.indirect(result) // For these types, size has a special meaning switch dtype { case _Bool: return d.unmarshalBool(size, offset, result) case _Map: return d.unmarshalMap(size, offset, result, depth) case _Pointer: return d.unmarshalPointer(size, offset, result, depth) case _Slice: return d.unmarshalSlice(size, offset, result, depth) } // For the remaining types, size is the byte size if offset+size > uint(len(d.buffer)) { return 0, newOffsetError() } switch dtype { case _Bytes: return d.unmarshalBytes(size, offset, result) case _Float32: return d.unmarshalFloat32(size, offset, result) case _Float64: return d.unmarshalFloat64(size, offset, result) case _Int32: return d.unmarshalInt32(size, offset, result) case _String: return d.unmarshalString(size, offset, result) case _Uint16: return d.unmarshalUint(size, offset, result, 16) case _Uint32: return d.unmarshalUint(size, offset, result, 32) case _Uint64: return d.unmarshalUint(size, offset, result, 64) case _Uint128: return d.unmarshalUint128(size, offset, result) default: return 0, newInvalidDatabaseError("unknown type: %d", dtype) } } func (d *decoder) decodeFromTypeToDeserializer( dtype dataType, size uint, offset uint, dser deserializer, depth int, ) (uint, error) { // For these types, size has a special meaning switch dtype { case _Bool: v, offset := d.decodeBool(size, offset) return offset, dser.Bool(v) case _Map: return d.decodeMapToDeserializer(size, offset, dser, depth) case _Pointer: pointer, newOffset, err := d.decodePointer(size, offset) if err != nil { return 0, err } _, err = d.decodeToDeserializer(pointer, dser, depth) return newOffset, err case _Slice: return d.decodeSliceToDeserializer(size, offset, dser, depth) } // For the remaining types, size is the byte size if offset+size > uint(len(d.buffer)) { return 0, newOffsetError() } switch dtype { case _Bytes: v, offset := d.decodeBytes(size, offset) return offset, dser.Bytes(v) case _Float32: v, offset := d.decodeFloat32(size, offset) return offset, dser.Float32(v) case _Float64: v, offset := d.decodeFloat64(size, offset) return offset, dser.Float64(v) case _Int32: v, offset := d.decodeInt(size, offset) return offset, dser.Int32(int32(v)) case _String: v, offset := d.decodeString(size, offset) return offset, dser.String(v) case _Uint16: v, offset := d.decodeUint(size, offset) return offset, dser.Uint16(uint16(v)) case _Uint32: v, offset := d.decodeUint(size, offset) return offset, dser.Uint32(uint32(v)) case _Uint64: v, offset := d.decodeUint(size, offset) return offset, dser.Uint64(v) case _Uint128: v, offset := d.decodeUint128(size, offset) return offset, dser.Uint128(v) default: return 0, newInvalidDatabaseError("unknown type: %d", dtype) } } func (d *decoder) unmarshalBool(size, offset uint, result reflect.Value) (uint, error) { if size > 1 { return 0, newInvalidDatabaseError("the MaxMind DB file's data section contains bad data (bool size of %v)", size) } value, newOffset := d.decodeBool(size, offset) switch result.Kind() { case reflect.Bool: result.SetBool(value) return newOffset, nil case reflect.Interface: if result.NumMethod() == 0 { result.Set(reflect.ValueOf(value)) return newOffset, nil } } return newOffset, newUnmarshalTypeError(value, result.Type()) } // indirect follows pointers and create values as necessary. This is // heavily based on encoding/json as my original version had a subtle // bug. This method should be considered to be licensed under // https://golang.org/LICENSE func (d *decoder) indirect(result reflect.Value) reflect.Value { for { // Load value from interface, but only if the result will be // usefully addressable. if result.Kind() == reflect.Interface && !result.IsNil() { e := result.Elem() if e.Kind() == reflect.Ptr && !e.IsNil() { result = e continue } } if result.Kind() != reflect.Ptr { break } if result.IsNil() { result.Set(reflect.New(result.Type().Elem())) } result = result.Elem() } return result } var sliceType = reflect.TypeOf([]byte{}) func (d *decoder) unmarshalBytes(size, offset uint, result reflect.Value) (uint, error) { value, newOffset := d.decodeBytes(size, offset) switch result.Kind() { case reflect.Slice: if result.Type() == sliceType { result.SetBytes(value) return newOffset, nil } case reflect.Interface: if result.NumMethod() == 0 { result.Set(reflect.ValueOf(value)) return newOffset, nil } } return newOffset, newUnmarshalTypeError(value, result.Type()) } func (d *decoder) unmarshalFloat32(size, offset uint, result reflect.Value) (uint, error) { if size != 4 { return 0, newInvalidDatabaseError("the MaxMind DB file's data section contains bad data (float32 size of %v)", size) } value, newOffset := d.decodeFloat32(size, offset) switch result.Kind() { case reflect.Float32, reflect.Float64: result.SetFloat(float64(value)) return newOffset, nil case reflect.Interface: if result.NumMethod() == 0 { result.Set(reflect.ValueOf(value)) return newOffset, nil } } return newOffset, newUnmarshalTypeError(value, result.Type()) } func (d *decoder) unmarshalFloat64(size, offset uint, result reflect.Value) (uint, error) { if size != 8 { return 0, newInvalidDatabaseError("the MaxMind DB file's data section contains bad data (float 64 size of %v)", size) } value, newOffset := d.decodeFloat64(size, offset) switch result.Kind() { case reflect.Float32, reflect.Float64: if result.OverflowFloat(value) { return 0, newUnmarshalTypeError(value, result.Type()) } result.SetFloat(value) return newOffset, nil case reflect.Interface: if result.NumMethod() == 0 { result.Set(reflect.ValueOf(value)) return newOffset, nil } } return newOffset, newUnmarshalTypeError(value, result.Type()) } func (d *decoder) unmarshalInt32(size, offset uint, result reflect.Value) (uint, error) { if size > 4 { return 0, newInvalidDatabaseError("the MaxMind DB file's data section contains bad data (int32 size of %v)", size) } value, newOffset := d.decodeInt(size, offset) switch result.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: n := int64(value) if !result.OverflowInt(n) { result.SetInt(n) return newOffset, nil } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: n := uint64(value) if !result.OverflowUint(n) { result.SetUint(n) return newOffset, nil } case reflect.Interface: if result.NumMethod() == 0 { result.Set(reflect.ValueOf(value)) return newOffset, nil } } return newOffset, newUnmarshalTypeError(value, result.Type()) } func (d *decoder) unmarshalMap( size uint, offset uint, result reflect.Value, depth int, ) (uint, error) { result = d.indirect(result) switch result.Kind() { default: return 0, newUnmarshalTypeError("map", result.Type()) case reflect.Struct: return d.decodeStruct(size, offset, result, depth) case reflect.Map: return d.decodeMap(size, offset, result, depth) case reflect.Interface: if result.NumMethod() == 0 { rv := reflect.ValueOf(make(map[string]interface{}, size)) newOffset, err := d.decodeMap(size, offset, rv, depth) result.Set(rv) return newOffset, err } return 0, newUnmarshalTypeError("map", result.Type()) } } func (d *decoder) unmarshalPointer(size, offset uint, result reflect.Value, depth int) (uint, error) { pointer, newOffset, err := d.decodePointer(size, offset) if err != nil { return 0, err } _, err = d.decode(pointer, result, depth) return newOffset, err } func (d *decoder) unmarshalSlice( size uint, offset uint, result reflect.Value, depth int, ) (uint, error) { switch result.Kind() { case reflect.Slice: return d.decodeSlice(size, offset, result, depth) case reflect.Interface: if result.NumMethod() == 0 { a := []interface{}{} rv := reflect.ValueOf(&a).Elem() newOffset, err := d.decodeSlice(size, offset, rv, depth) result.Set(rv) return newOffset, err } } return 0, newUnmarshalTypeError("array", result.Type()) } func (d *decoder) unmarshalString(size, offset uint, result reflect.Value) (uint, error) { value, newOffset := d.decodeString(size, offset) switch result.Kind() { case reflect.String: result.SetString(value) return newOffset, nil case reflect.Interface: if result.NumMethod() == 0 { result.Set(reflect.ValueOf(value)) return newOffset, nil } } return newOffset, newUnmarshalTypeError(value, result.Type()) } func (d *decoder) unmarshalUint(size, offset uint, result reflect.Value, uintType uint) (uint, error) { if size > uintType/8 { return 0, newInvalidDatabaseError("the MaxMind DB file's data section contains bad data (uint%v size of %v)", uintType, size) } value, newOffset := d.decodeUint(size, offset) switch result.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: n := int64(value) if !result.OverflowInt(n) { result.SetInt(n) return newOffset, nil } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: if !result.OverflowUint(value) { result.SetUint(value) return newOffset, nil } case reflect.Interface: if result.NumMethod() == 0 { result.Set(reflect.ValueOf(value)) return newOffset, nil } } return newOffset, newUnmarshalTypeError(value, result.Type()) } var bigIntType = reflect.TypeOf(big.Int{}) func (d *decoder) unmarshalUint128(size, offset uint, result reflect.Value) (uint, error) { if size > 16 { return 0, newInvalidDatabaseError("the MaxMind DB file's data section contains bad data (uint128 size of %v)", size) } value, newOffset := d.decodeUint128(size, offset) switch result.Kind() { case reflect.Struct: if result.Type() == bigIntType { result.Set(reflect.ValueOf(*value)) return newOffset, nil } case reflect.Interface: if result.NumMethod() == 0 { result.Set(reflect.ValueOf(value)) return newOffset, nil } } return newOffset, newUnmarshalTypeError(value, result.Type()) } func (d *decoder) decodeBool(size, offset uint) (bool, uint) { return size != 0, offset } func (d *decoder) decodeBytes(size, offset uint) ([]byte, uint) { newOffset := offset + size bytes := make([]byte, size) copy(bytes, d.buffer[offset:newOffset]) return bytes, newOffset } func (d *decoder) decodeFloat64(size, offset uint) (float64, uint) { newOffset := offset + size bits := binary.BigEndian.Uint64(d.buffer[offset:newOffset]) return math.Float64frombits(bits), newOffset } func (d *decoder) decodeFloat32(size, offset uint) (float32, uint) { newOffset := offset + size bits := binary.BigEndian.Uint32(d.buffer[offset:newOffset]) return math.Float32frombits(bits), newOffset } func (d *decoder) decodeInt(size, offset uint) (int, uint) { newOffset := offset + size var val int32 for _, b := range d.buffer[offset:newOffset] { val = (val << 8) | int32(b) } return int(val), newOffset } func (d *decoder) decodeMap( size uint, offset uint, result reflect.Value, depth int, ) (uint, error) { if result.IsNil() { result.Set(reflect.MakeMapWithSize(result.Type(), int(size))) } mapType := result.Type() keyValue := reflect.New(mapType.Key()).Elem() elemType := mapType.Elem() elemKind := elemType.Kind() var elemValue reflect.Value for i := uint(0); i < size; i++ { var key []byte var err error key, offset, err = d.decodeKey(offset) if err != nil { return 0, err } if !elemValue.IsValid() || elemKind == reflect.Interface { elemValue = reflect.New(elemType).Elem() } offset, err = d.decode(offset, elemValue, depth) if err != nil { return 0, err } keyValue.SetString(string(key)) result.SetMapIndex(keyValue, elemValue) } return offset, nil } func (d *decoder) decodeMapToDeserializer( size uint, offset uint, dser deserializer, depth int, ) (uint, error) { err := dser.StartMap(size) if err != nil { return 0, err } for i := uint(0); i < size; i++ { // TODO - implement key/value skipping? offset, err = d.decodeToDeserializer(offset, dser, depth) if err != nil { return 0, err } offset, err = d.decodeToDeserializer(offset, dser, depth) if err != nil { return 0, err } } err = dser.End() if err != nil { return 0, err } return offset, nil } func (d *decoder) decodePointer( size uint, offset uint, ) (uint, uint, error) { pointerSize := ((size >> 3) & 0x3) + 1 newOffset := offset + pointerSize if newOffset > uint(len(d.buffer)) { return 0, 0, newOffsetError() } pointerBytes := d.buffer[offset:newOffset] var prefix uint if pointerSize == 4 { prefix = 0 } else { prefix = size & 0x7 } unpacked := uintFromBytes(prefix, pointerBytes) var pointerValueOffset uint switch pointerSize { case 1: pointerValueOffset = 0 case 2: pointerValueOffset = 2048 case 3: pointerValueOffset = 526336 case 4: pointerValueOffset = 0 } pointer := unpacked + pointerValueOffset return pointer, newOffset, nil } func (d *decoder) decodeSlice( size uint, offset uint, result reflect.Value, depth int, ) (uint, error) { result.Set(reflect.MakeSlice(result.Type(), int(size), int(size))) for i := 0; i < int(size); i++ { var err error offset, err = d.decode(offset, result.Index(i), depth) if err != nil { return 0, err } } return offset, nil } func (d *decoder) decodeSliceToDeserializer( size uint, offset uint, dser deserializer, depth int, ) (uint, error) { err := dser.StartSlice(size) if err != nil { return 0, err } for i := uint(0); i < size; i++ { offset, err = d.decodeToDeserializer(offset, dser, depth) if err != nil { return 0, err } } err = dser.End() if err != nil { return 0, err } return offset, nil } func (d *decoder) decodeString(size, offset uint) (string, uint) { newOffset := offset + size return string(d.buffer[offset:newOffset]), newOffset } func (d *decoder) decodeStruct( size uint, offset uint, result reflect.Value, depth int, ) (uint, error) { fields := cachedFields(result) // This fills in embedded structs for _, i := range fields.anonymousFields { _, err := d.unmarshalMap(size, offset, result.Field(i), depth) if err != nil { return 0, err } } // This handles named fields for i := uint(0); i < size; i++ { var ( err error key []byte ) key, offset, err = d.decodeKey(offset) if err != nil { return 0, err } // The string() does not create a copy due to this compiler // optimization: https://github.com/golang/go/issues/3512 j, ok := fields.namedFields[string(key)] if !ok { offset, err = d.nextValueOffset(offset, 1) if err != nil { return 0, err } continue } offset, err = d.decode(offset, result.Field(j), depth) if err != nil { return 0, err } } return offset, nil } type fieldsType struct { namedFields map[string]int anonymousFields []int } var fieldsMap sync.Map func cachedFields(result reflect.Value) *fieldsType { resultType := result.Type() if fields, ok := fieldsMap.Load(resultType); ok { return fields.(*fieldsType) } numFields := resultType.NumField() namedFields := make(map[string]int, numFields) var anonymous []int for i := 0; i < numFields; i++ { field := resultType.Field(i) fieldName := field.Name if tag := field.Tag.Get("maxminddb"); tag != "" { if tag == "-" { continue } fieldName = tag } if field.Anonymous { anonymous = append(anonymous, i) continue } namedFields[fieldName] = i } fields := &fieldsType{namedFields, anonymous} fieldsMap.Store(resultType, fields) return fields } func (d *decoder) decodeUint(size, offset uint) (uint64, uint) { newOffset := offset + size bytes := d.buffer[offset:newOffset] var val uint64 for _, b := range bytes { val = (val << 8) | uint64(b) } return val, newOffset } func (d *decoder) decodeUint128(size, offset uint) (*big.Int, uint) { newOffset := offset + size val := new(big.Int) val.SetBytes(d.buffer[offset:newOffset]) return val, newOffset } func uintFromBytes(prefix uint, uintBytes []byte) uint { val := prefix for _, b := range uintBytes { val = (val << 8) | uint(b) } return val } // decodeKey decodes a map key into []byte slice. We use a []byte so that we // can take advantage of https://github.com/golang/go/issues/3512 to avoid // copying the bytes when decoding a struct. Previously, we achieved this by // using unsafe. func (d *decoder) decodeKey(offset uint) ([]byte, uint, error) { typeNum, size, dataOffset, err := d.decodeCtrlData(offset) if err != nil { return nil, 0, err } if typeNum == _Pointer { pointer, ptrOffset, err := d.decodePointer(size, dataOffset) if err != nil { return nil, 0, err } key, _, err := d.decodeKey(pointer) return key, ptrOffset, err } if typeNum != _String { return nil, 0, newInvalidDatabaseError("unexpected type when decoding string: %v", typeNum) } newOffset := dataOffset + size if newOffset > uint(len(d.buffer)) { return nil, 0, newOffsetError() } return d.buffer[dataOffset:newOffset], newOffset, nil } // This function is used to skip ahead to the next value without decoding // the one at the offset passed in. The size bits have different meanings for // different data types func (d *decoder) nextValueOffset(offset, numberToSkip uint) (uint, error) { if numberToSkip == 0 { return offset, nil } typeNum, size, offset, err := d.decodeCtrlData(offset) if err != nil { return 0, err } switch typeNum { case _Pointer: _, offset, err = d.decodePointer(size, offset) if err != nil { return 0, err } case _Map: numberToSkip += 2 * size case _Slice: numberToSkip += size case _Bool: default: offset += size } return d.nextValueOffset(offset, numberToSkip-1) } maxminddb-golang-1.8.0/decoder_test.go000066400000000000000000000144731371752156100177440ustar00rootroot00000000000000package maxminddb import ( "encoding/hex" "io/ioutil" "math/big" "reflect" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestBool(t *testing.T) { bools := map[string]interface{}{ "0007": false, "0107": true, } validateDecoding(t, bools) } func TestDouble(t *testing.T) { doubles := map[string]interface{}{ "680000000000000000": 0.0, "683FE0000000000000": 0.5, "68400921FB54442EEA": 3.14159265359, "68405EC00000000000": 123.0, "6841D000000007F8F4": 1073741824.12457, "68BFE0000000000000": -0.5, "68C00921FB54442EEA": -3.14159265359, "68C1D000000007F8F4": -1073741824.12457, } validateDecoding(t, doubles) } func TestFloat(t *testing.T) { floats := map[string]interface{}{ "040800000000": float32(0.0), "04083F800000": float32(1.0), "04083F8CCCCD": float32(1.1), "04084048F5C3": float32(3.14), "0408461C3FF6": float32(9999.99), "0408BF800000": float32(-1.0), "0408BF8CCCCD": float32(-1.1), "0408C048F5C3": -float32(3.14), "0408C61C3FF6": float32(-9999.99), } validateDecoding(t, floats) } func TestInt32(t *testing.T) { int32 := map[string]interface{}{ "0001": 0, "0401ffffffff": -1, "0101ff": 255, "0401ffffff01": -255, "020101f4": 500, "0401fffffe0c": -500, "0201ffff": 65535, "0401ffff0001": -65535, "0301ffffff": 16777215, "0401ff000001": -16777215, "04017fffffff": 2147483647, "040180000001": -2147483647, } validateDecoding(t, int32) } func TestMap(t *testing.T) { maps := map[string]interface{}{ "e0": map[string]interface{}{}, "e142656e43466f6f": map[string]interface{}{"en": "Foo"}, "e242656e43466f6f427a6843e4baba": map[string]interface{}{"en": "Foo", "zh": "人"}, "e1446e616d65e242656e43466f6f427a6843e4baba": map[string]interface{}{"name": map[string]interface{}{"en": "Foo", "zh": "人"}}, "e1496c616e677561676573020442656e427a68": map[string]interface{}{"languages": []interface{}{"en", "zh"}}, } validateDecoding(t, maps) } func TestSlice(t *testing.T) { slice := map[string]interface{}{ "0004": []interface{}{}, "010443466f6f": []interface{}{"Foo"}, "020443466f6f43e4baba": []interface{}{"Foo", "人"}, } validateDecoding(t, slice) } var testStrings = makeTestStrings() func makeTestStrings() map[string]interface{} { str := map[string]interface{}{ "40": "", "4131": "1", "43E4BABA": "人", "5b313233343536373839303132333435363738393031323334353637": "123456789012345678901234567", "5c31323334353637383930313233343536373839303132333435363738": "1234567890123456789012345678", "5d003132333435363738393031323334353637383930313233343536373839": "12345678901234567890123456789", "5d01313233343536373839303132333435363738393031323334353637383930": "123456789012345678901234567890", } for k, v := range map[string]int{"5e00d7": 500, "5e06b3": 2000, "5f001053": 70000} { key := k + strings.Repeat("78", v) str[key] = strings.Repeat("x", v) } return str } func TestString(t *testing.T) { validateDecoding(t, testStrings) } func TestByte(t *testing.T) { b := make(map[string]interface{}) for key, val := range testStrings { oldCtrl, _ := hex.DecodeString(key[0:2]) newCtrl := []byte{oldCtrl[0] ^ 0xc0} key = strings.Replace(key, hex.EncodeToString(oldCtrl), hex.EncodeToString(newCtrl), 1) b[key] = []byte(val.(string)) } validateDecoding(t, b) } func TestUint16(t *testing.T) { uint16 := map[string]interface{}{ "a0": uint64(0), "a1ff": uint64(255), "a201f4": uint64(500), "a22a78": uint64(10872), "a2ffff": uint64(65535), } validateDecoding(t, uint16) } func TestUint32(t *testing.T) { uint32 := map[string]interface{}{ "c0": uint64(0), "c1ff": uint64(255), "c201f4": uint64(500), "c22a78": uint64(10872), "c2ffff": uint64(65535), "c3ffffff": uint64(16777215), "c4ffffffff": uint64(4294967295), } validateDecoding(t, uint32) } func TestUint64(t *testing.T) { ctrlByte := "02" bits := uint64(64) uints := map[string]interface{}{ "00" + ctrlByte: uint64(0), "02" + ctrlByte + "01f4": uint64(500), "02" + ctrlByte + "2a78": uint64(10872), } for i := uint64(0); i <= bits/8; i++ { expected := uint64((1 << (8 * i)) - 1) input := hex.EncodeToString([]byte{byte(i)}) + ctrlByte + strings.Repeat("ff", int(i)) uints[input] = expected } validateDecoding(t, uints) } // Dedup with above somehow func TestUint128(t *testing.T) { ctrlByte := "03" bits := uint(128) uints := map[string]interface{}{ "00" + ctrlByte: big.NewInt(0), "02" + ctrlByte + "01f4": big.NewInt(500), "02" + ctrlByte + "2a78": big.NewInt(10872), } for i := uint(1); i <= bits/8; i++ { expected := powBigInt(big.NewInt(2), 8*i) expected = expected.Sub(expected, big.NewInt(1)) input := hex.EncodeToString([]byte{byte(i)}) + ctrlByte + strings.Repeat("ff", int(i)) uints[input] = expected } validateDecoding(t, uints) } // No pow or bit shifting for big int, apparently :-( // This is _not_ meant to be a comprehensive power function func powBigInt(bi *big.Int, pow uint) *big.Int { newInt := big.NewInt(1) for i := uint(0); i < pow; i++ { newInt.Mul(newInt, bi) } return newInt } func validateDecoding(t *testing.T, tests map[string]interface{}) { for inputStr, expected := range tests { inputBytes, _ := hex.DecodeString(inputStr) d := decoder{inputBytes} var result interface{} _, err := d.decode(0, reflect.ValueOf(&result), 0) assert.NoError(t, err) if !reflect.DeepEqual(result, expected) { // A big case statement would produce nicer errors t.Errorf("Output was incorrect: %s %s", inputStr, expected) } } } func TestPointers(t *testing.T) { bytes, err := ioutil.ReadFile(testFile("maps-with-pointers.raw")) require.NoError(t, err) d := decoder{bytes} expected := map[uint]map[string]string{ 0: {"long_key": "long_value1"}, 22: {"long_key": "long_value2"}, 37: {"long_key2": "long_value1"}, 50: {"long_key2": "long_value2"}, 55: {"long_key": "long_value1"}, 57: {"long_key2": "long_value2"}, } for offset, expectedValue := range expected { var actual map[string]string _, err := d.decode(offset, reflect.ValueOf(&actual), 0) assert.NoError(t, err) if !reflect.DeepEqual(actual, expectedValue) { t.Errorf("Decode for pointer at %d failed", offset) } } } maxminddb-golang-1.8.0/deserializer.go000066400000000000000000000016671371752156100177630ustar00rootroot00000000000000package maxminddb import "math/big" // deserializer is an interface for a type that deserializes an MaxMind DB // data record to some other type. This exists as an alternative to the // standard reflection API. // // This is fundamentally different than the Unmarshaler interface that // several packages provide. A Deserializer will generally create the // final struct or value rather than unmarshaling to itself. // // This interface and the associated unmarshaling code is EXPERIMENTAL! // It is not currently covered by any Semantic Versioning guarantees. // Use at your own risk. type deserializer interface { ShouldSkip(offset uintptr) (bool, error) StartSlice(size uint) error StartMap(size uint) error End() error String(string) error Float64(float64) error Bytes([]byte) error Uint16(uint16) error Uint32(uint32) error Int32(int32) error Uint64(uint64) error Uint128(*big.Int) error Bool(bool) error Float32(float32) error } maxminddb-golang-1.8.0/deserializer.go deserializer_test.go000066400000000000000000000044051371752156100240620ustar00rootroot00000000000000package maxminddb import ( "math/big" "net" "testing" "github.com/stretchr/testify/require" ) func TestDecodingToDeserializer(t *testing.T) { reader, err := Open(testFile("MaxMind-DB-test-decoder.mmdb")) require.NoError(t, err, "unexpected error while opening database: %v", err) dser := testDeserializer{} err = reader.Lookup(net.ParseIP("::1.1.1.0"), &dser) require.NoError(t, err, "unexpected error while doing lookup: %v", err) checkDecodingToInterface(t, dser.rv) } type stackValue struct { value interface{} curNum int } type testDeserializer struct { stack []*stackValue rv interface{} key *string } func (d *testDeserializer) ShouldSkip(offset uintptr) (bool, error) { return false, nil } func (d *testDeserializer) StartSlice(size uint) error { return d.add(make([]interface{}, size)) } func (d *testDeserializer) StartMap(size uint) error { return d.add(map[string]interface{}{}) } func (d *testDeserializer) End() error { d.stack = d.stack[:len(d.stack)-1] return nil } func (d *testDeserializer) String(v string) error { return d.add(v) } func (d *testDeserializer) Float64(v float64) error { return d.add(v) } func (d *testDeserializer) Bytes(v []byte) error { return d.add(v) } func (d *testDeserializer) Uint16(v uint16) error { return d.add(uint64(v)) } func (d *testDeserializer) Uint32(v uint32) error { return d.add(uint64(v)) } func (d *testDeserializer) Int32(v int32) error { return d.add(int(v)) } func (d *testDeserializer) Uint64(v uint64) error { return d.add(v) } func (d *testDeserializer) Uint128(v *big.Int) error { return d.add(v) } func (d *testDeserializer) Bool(v bool) error { return d.add(v) } func (d *testDeserializer) Float32(v float32) error { return d.add(v) } func (d *testDeserializer) add(v interface{}) error { if len(d.stack) == 0 { d.rv = v } else { top := d.stack[len(d.stack)-1] switch parent := top.value.(type) { case map[string]interface{}: if d.key == nil { key := v.(string) d.key = &key } else { parent[*d.key] = v d.key = nil } case []interface{}: parent[top.curNum] = v top.curNum++ default: } } switch v := v.(type) { case map[string]interface{}, []interface{}: d.stack = append(d.stack, &stackValue{value: v}) default: } return nil } maxminddb-golang-1.8.0/errors.go000066400000000000000000000021711371752156100166040ustar00rootroot00000000000000package maxminddb import ( "fmt" "reflect" ) // InvalidDatabaseError is returned when the database contains invalid data // and cannot be parsed. type InvalidDatabaseError struct { message string } func newOffsetError() InvalidDatabaseError { return InvalidDatabaseError{"unexpected end of database"} } func newInvalidDatabaseError(format string, args ...interface{}) InvalidDatabaseError { return InvalidDatabaseError{fmt.Sprintf(format, args...)} } func (e InvalidDatabaseError) Error() string { return e.message } // UnmarshalTypeError is returned when the value in the database cannot be // assigned to the specified data type. type UnmarshalTypeError struct { Value string // stringified copy of the database value that caused the error Type reflect.Type // type of the value that could not be assign to } func newUnmarshalTypeError(value interface{}, rType reflect.Type) UnmarshalTypeError { return UnmarshalTypeError{ Value: fmt.Sprintf("%v", value), Type: rType, } } func (e UnmarshalTypeError) Error() string { return fmt.Sprintf("maxminddb: cannot unmarshal %s into type %s", e.Value, e.Type.String()) } maxminddb-golang-1.8.0/example_test.go000066400000000000000000000060751371752156100177710ustar00rootroot00000000000000package maxminddb_test import ( "fmt" "log" "net" "github.com/oschwald/maxminddb-golang" ) // This example shows how to decode to a struct func ExampleReader_Lookup_struct() { db, err := maxminddb.Open("test-data/test-data/GeoIP2-City-Test.mmdb") if err != nil { log.Fatal(err) } defer db.Close() ip := net.ParseIP("81.2.69.142") var record struct { Country struct { ISOCode string `maxminddb:"iso_code"` } `maxminddb:"country"` } // Or any appropriate struct err = db.Lookup(ip, &record) if err != nil { log.Fatal(err) } fmt.Print(record.Country.ISOCode) // Output: // GB } // This example demonstrates how to decode to an interface{} func ExampleReader_Lookup_interface() { db, err := maxminddb.Open("test-data/test-data/GeoIP2-City-Test.mmdb") if err != nil { log.Fatal(err) } defer db.Close() ip := net.ParseIP("81.2.69.142") var record interface{} err = db.Lookup(ip, &record) if err != nil { log.Fatal(err) } fmt.Printf("%v", record) } // This example demonstrates how to iterate over all networks in the // database func ExampleReader_Networks() { db, err := maxminddb.Open("test-data/test-data/GeoIP2-Connection-Type-Test.mmdb") if err != nil { log.Fatal(err) } defer db.Close() record := struct { Domain string `maxminddb:"connection_type"` }{} networks := db.Networks(maxminddb.SkipAliasedNetworks) for networks.Next() { subnet, err := networks.Network(&record) if err != nil { log.Fatal(err) } fmt.Printf("%s: %s\n", subnet.String(), record.Domain) } if networks.Err() != nil { log.Fatal(networks.Err()) } // Output: // 1.0.0.0/24: Dialup // 1.0.1.0/24: Cable/DSL // 1.0.2.0/23: Dialup // 1.0.4.0/22: Dialup // 1.0.8.0/21: Dialup // 1.0.16.0/20: Dialup // 1.0.32.0/19: Dialup // 1.0.64.0/18: Dialup // 1.0.128.0/17: Dialup // 80.214.0.0/20: Cellular // 96.1.0.0/16: Cable/DSL // 96.10.0.0/15: Cable/DSL // 96.69.0.0/16: Cable/DSL // 96.94.0.0/15: Cable/DSL // 108.96.0.0/11: Cellular // 175.16.199.0/24: Dialup // 187.156.138.0/24: Cable/DSL // 201.243.200.0/24: Corporate // 207.179.48.0/20: Cellular // 2003::/24: Cable/DSL } // This example demonstrates how to iterate over all networks in the // database which are contained within an arbitrary network. func ExampleReader_NetworksWithin() { db, err := maxminddb.Open("test-data/test-data/GeoIP2-Connection-Type-Test.mmdb") if err != nil { log.Fatal(err) } defer db.Close() record := struct { Domain string `maxminddb:"connection_type"` }{} _, network, err := net.ParseCIDR("1.0.0.0/8") if err != nil { log.Fatal(err) } networks := db.NetworksWithin(network, maxminddb.SkipAliasedNetworks) for networks.Next() { subnet, err := networks.Network(&record) if err != nil { log.Fatal(err) } fmt.Printf("%s: %s\n", subnet.String(), record.Domain) } if networks.Err() != nil { log.Fatal(networks.Err()) } // Output: // 1.0.0.0/24: Dialup // 1.0.1.0/24: Cable/DSL // 1.0.2.0/23: Dialup // 1.0.4.0/22: Dialup // 1.0.8.0/21: Dialup // 1.0.16.0/20: Dialup // 1.0.32.0/19: Dialup // 1.0.64.0/18: Dialup // 1.0.128.0/17: Dialup } maxminddb-golang-1.8.0/go.mod000066400000000000000000000002321371752156100160430ustar00rootroot00000000000000module github.com/oschwald/maxminddb-golang go 1.9 require ( github.com/stretchr/testify v1.6.1 golang.org/x/sys v0.0.0-20191224085550-c709ea063b76 ) maxminddb-golang-1.8.0/go.sum000066400000000000000000000024371371752156100161010ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76 h1:Dho5nD6R3PcW2SH1or8vS0dszDaXRxIw55lBX7XiE5g= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maxminddb-golang-1.8.0/mmap_unix.go000066400000000000000000000004251371752156100172650ustar00rootroot00000000000000// +build !windows,!appengine,!plan9 package maxminddb import ( "golang.org/x/sys/unix" ) func mmap(fd, length int) (data []byte, err error) { return unix.Mmap(fd, 0, length, unix.PROT_READ, unix.MAP_SHARED) } func munmap(b []byte) (err error) { return unix.Munmap(b) } maxminddb-golang-1.8.0/mmap_windows.go000066400000000000000000000034221371752156100177740ustar00rootroot00000000000000// +build windows,!appengine package maxminddb // Windows support largely borrowed from mmap-go. // // Copyright 2011 Evan Shaw. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. import ( "errors" "os" "reflect" "sync" "unsafe" "golang.org/x/sys/windows" ) type memoryMap []byte // Windows var handleLock sync.Mutex var handleMap = map[uintptr]windows.Handle{} func mmap(fd int, length int) (data []byte, err error) { h, errno := windows.CreateFileMapping(windows.Handle(fd), nil, uint32(windows.PAGE_READONLY), 0, uint32(length), nil) if h == 0 { return nil, os.NewSyscallError("CreateFileMapping", errno) } addr, errno := windows.MapViewOfFile(h, uint32(windows.FILE_MAP_READ), 0, 0, uintptr(length)) if addr == 0 { return nil, os.NewSyscallError("MapViewOfFile", errno) } handleLock.Lock() handleMap[addr] = h handleLock.Unlock() m := memoryMap{} dh := m.header() dh.Data = addr dh.Len = length dh.Cap = dh.Len return m, nil } func (m *memoryMap) header() *reflect.SliceHeader { return (*reflect.SliceHeader)(unsafe.Pointer(m)) } func flush(addr, len uintptr) error { errno := windows.FlushViewOfFile(addr, len) return os.NewSyscallError("FlushViewOfFile", errno) } func munmap(b []byte) (err error) { m := memoryMap(b) dh := m.header() addr := dh.Data length := uintptr(dh.Len) flush(addr, length) err = windows.UnmapViewOfFile(addr) if err != nil { return err } handleLock.Lock() defer handleLock.Unlock() handle, ok := handleMap[addr] if !ok { // should be impossible; we would've errored above return errors.New("unknown base address") } delete(handleMap, addr) e := windows.CloseHandle(windows.Handle(handle)) return os.NewSyscallError("CloseHandle", e) } maxminddb-golang-1.8.0/node.go000066400000000000000000000026261371752156100162220ustar00rootroot00000000000000package maxminddb type nodeReader interface { readLeft(uint) uint readRight(uint) uint } type nodeReader24 struct { buffer []byte } func (n nodeReader24) readLeft(nodeNumber uint) uint { return (uint(n.buffer[nodeNumber]) << 16) | (uint(n.buffer[nodeNumber+1]) << 8) | uint(n.buffer[nodeNumber+2]) } func (n nodeReader24) readRight(nodeNumber uint) uint { return (uint(n.buffer[nodeNumber+3]) << 16) | (uint(n.buffer[nodeNumber+4]) << 8) | uint(n.buffer[nodeNumber+5]) } type nodeReader28 struct { buffer []byte } func (n nodeReader28) readLeft(nodeNumber uint) uint { return ((uint(n.buffer[nodeNumber+3]) & 0xF0) << 20) | (uint(n.buffer[nodeNumber]) << 16) | (uint(n.buffer[nodeNumber+1]) << 8) | uint(n.buffer[nodeNumber+2]) } func (n nodeReader28) readRight(nodeNumber uint) uint { return ((uint(n.buffer[nodeNumber+3]) & 0x0F) << 24) | (uint(n.buffer[nodeNumber+4]) << 16) | (uint(n.buffer[nodeNumber+5]) << 8) | uint(n.buffer[nodeNumber+6]) } type nodeReader32 struct { buffer []byte } func (n nodeReader32) readLeft(nodeNumber uint) uint { return (uint(n.buffer[nodeNumber]) << 24) | (uint(n.buffer[nodeNumber+1]) << 16) | (uint(n.buffer[nodeNumber+2]) << 8) | uint(n.buffer[nodeNumber+3]) } func (n nodeReader32) readRight(nodeNumber uint) uint { return (uint(n.buffer[nodeNumber+4]) << 24) | (uint(n.buffer[nodeNumber+5]) << 16) | (uint(n.buffer[nodeNumber+6]) << 8) | uint(n.buffer[nodeNumber+7]) } maxminddb-golang-1.8.0/reader.go000066400000000000000000000236541371752156100165430ustar00rootroot00000000000000// Package maxminddb provides a reader for the MaxMind DB file format. package maxminddb import ( "bytes" "errors" "fmt" "net" "reflect" ) const ( // NotFound is returned by LookupOffset when a matched root record offset // cannot be found. NotFound = ^uintptr(0) dataSectionSeparatorSize = 16 ) var metadataStartMarker = []byte("\xAB\xCD\xEFMaxMind.com") // Reader holds the data corresponding to the MaxMind DB file. Its only public // field is Metadata, which contains the metadata from the MaxMind DB file. // // All of the methods on Reader are thread-safe. The struct may be safely // shared across goroutines. type Reader struct { hasMappedFile bool buffer []byte nodeReader nodeReader decoder decoder Metadata Metadata ipv4Start uint ipv4StartBitDepth int nodeOffsetMult uint } // Metadata holds the metadata decoded from the MaxMind DB file. In particular // it has the format version, the build time as Unix epoch time, the database // type and description, the IP version supported, and a slice of the natural // languages included. type Metadata struct { BinaryFormatMajorVersion uint `maxminddb:"binary_format_major_version"` BinaryFormatMinorVersion uint `maxminddb:"binary_format_minor_version"` BuildEpoch uint `maxminddb:"build_epoch"` DatabaseType string `maxminddb:"database_type"` Description map[string]string `maxminddb:"description"` IPVersion uint `maxminddb:"ip_version"` Languages []string `maxminddb:"languages"` NodeCount uint `maxminddb:"node_count"` RecordSize uint `maxminddb:"record_size"` } // FromBytes takes a byte slice corresponding to a MaxMind DB file and returns // a Reader structure or an error. func FromBytes(buffer []byte) (*Reader, error) { metadataStart := bytes.LastIndex(buffer, metadataStartMarker) if metadataStart == -1 { return nil, newInvalidDatabaseError("error opening database: invalid MaxMind DB file") } metadataStart += len(metadataStartMarker) metadataDecoder := decoder{buffer[metadataStart:]} var metadata Metadata rvMetdata := reflect.ValueOf(&metadata) _, err := metadataDecoder.decode(0, rvMetdata, 0) if err != nil { return nil, err } searchTreeSize := metadata.NodeCount * metadata.RecordSize / 4 dataSectionStart := searchTreeSize + dataSectionSeparatorSize dataSectionEnd := uint(metadataStart - len(metadataStartMarker)) if dataSectionStart > dataSectionEnd { return nil, newInvalidDatabaseError("the MaxMind DB contains invalid metadata") } d := decoder{ buffer[searchTreeSize+dataSectionSeparatorSize : metadataStart-len(metadataStartMarker)], } nodeBuffer := buffer[:searchTreeSize] var nodeReader nodeReader switch metadata.RecordSize { case 24: nodeReader = nodeReader24{buffer: nodeBuffer} case 28: nodeReader = nodeReader28{buffer: nodeBuffer} case 32: nodeReader = nodeReader32{buffer: nodeBuffer} default: return nil, newInvalidDatabaseError("unknown record size: %d", metadata.RecordSize) } reader := &Reader{ buffer: buffer, nodeReader: nodeReader, decoder: d, Metadata: metadata, ipv4Start: 0, nodeOffsetMult: metadata.RecordSize / 4, } reader.setIPv4Start() return reader, err } func (r *Reader) setIPv4Start() { if r.Metadata.IPVersion != 6 { return } nodeCount := r.Metadata.NodeCount node := uint(0) i := 0 for ; i < 96 && node < nodeCount; i++ { node = r.nodeReader.readLeft(node * r.nodeOffsetMult) } r.ipv4Start = node r.ipv4StartBitDepth = i } // Lookup retrieves the database record for ip and stores it in the value // pointed to by result. If result is nil or not a pointer, an error is // returned. If the data in the database record cannot be stored in result // because of type differences, an UnmarshalTypeError is returned. If the // database is invalid or otherwise cannot be read, an InvalidDatabaseError // is returned. func (r *Reader) Lookup(ip net.IP, result interface{}) error { if r.buffer == nil { return errors.New("cannot call Lookup on a closed database") } pointer, _, _, err := r.lookupPointer(ip) if pointer == 0 || err != nil { return err } return r.retrieveData(pointer, result) } // LookupNetwork retrieves the database record for ip and stores it in the // value pointed to by result. The network returned is the network associated // with the data record in the database. The ok return value indicates whether // the database contained a record for the ip. // // If result is nil or not a pointer, an error is returned. If the data in the // database record cannot be stored in result because of type differences, an // UnmarshalTypeError is returned. If the database is invalid or otherwise // cannot be read, an InvalidDatabaseError is returned. func (r *Reader) LookupNetwork(ip net.IP, result interface{}) (network *net.IPNet, ok bool, err error) { if r.buffer == nil { return nil, false, errors.New("cannot call Lookup on a closed database") } pointer, prefixLength, ip, err := r.lookupPointer(ip) network = r.cidr(ip, prefixLength) if pointer == 0 || err != nil { return network, false, err } return network, true, r.retrieveData(pointer, result) } // LookupOffset maps an argument net.IP to a corresponding record offset in the // database. NotFound is returned if no such record is found, and a record may // otherwise be extracted by passing the returned offset to Decode. LookupOffset // is an advanced API, which exists to provide clients with a means to cache // previously-decoded records. func (r *Reader) LookupOffset(ip net.IP) (uintptr, error) { if r.buffer == nil { return 0, errors.New("cannot call LookupOffset on a closed database") } pointer, _, _, err := r.lookupPointer(ip) if pointer == 0 || err != nil { return NotFound, err } return r.resolveDataPointer(pointer) } func (r *Reader) cidr(ip net.IP, prefixLength int) *net.IPNet { // This is necessary as the node that the IPv4 start is at may // be at a bit depth that is less that 96, i.e., ipv4Start points // to a leaf node. For instance, if a record was inserted at ::/8, // the ipv4Start would point directly at the leaf node for the // record and would have a bit depth of 8. This would not happen // with databases currently distributed by MaxMind as all of them // have an IPv4 subtree that is greater than a single node. if r.Metadata.IPVersion == 6 && len(ip) == net.IPv4len && r.ipv4StartBitDepth != 96 { return &net.IPNet{IP: net.ParseIP("::"), Mask: net.CIDRMask(r.ipv4StartBitDepth, 128)} } mask := net.CIDRMask(prefixLength, len(ip)*8) return &net.IPNet{IP: ip.Mask(mask), Mask: mask} } // Decode the record at |offset| into |result|. The result value pointed to // must be a data value that corresponds to a record in the database. This may // include a struct representation of the data, a map capable of holding the // data or an empty interface{} value. // // If result is a pointer to a struct, the struct need not include a field // for every value that may be in the database. If a field is not present in // the structure, the decoder will not decode that field, reducing the time // required to decode the record. // // As a special case, a struct field of type uintptr will be used to capture // the offset of the value. Decode may later be used to extract the stored // value from the offset. MaxMind DBs are highly normalized: for example in // the City database, all records of the same country will reference a // single representative record for that country. This uintptr behavior allows // clients to leverage this normalization in their own sub-record caching. func (r *Reader) Decode(offset uintptr, result interface{}) error { if r.buffer == nil { return errors.New("cannot call Decode on a closed database") } return r.decode(offset, result) } func (r *Reader) decode(offset uintptr, result interface{}) error { rv := reflect.ValueOf(result) if rv.Kind() != reflect.Ptr || rv.IsNil() { return errors.New("result param must be a pointer") } if dser, ok := result.(deserializer); ok { _, err := r.decoder.decodeToDeserializer(uint(offset), dser, 0) return err } _, err := r.decoder.decode(uint(offset), rv, 0) return err } func (r *Reader) lookupPointer(ip net.IP) (uint, int, net.IP, error) { if ip == nil { return 0, 0, ip, errors.New("IP passed to Lookup cannot be nil") } ipV4Address := ip.To4() if ipV4Address != nil { ip = ipV4Address } if len(ip) == 16 && r.Metadata.IPVersion == 4 { return 0, 0, ip, fmt.Errorf("error looking up '%s': you attempted to look up an IPv6 address in an IPv4-only database", ip.String()) } bitCount := uint(len(ip) * 8) var node uint if bitCount == 32 { node = r.ipv4Start } node, prefixLength := r.traverseTree(ip, node, bitCount) nodeCount := r.Metadata.NodeCount if node == nodeCount { // Record is empty return 0, prefixLength, ip, nil } else if node > nodeCount { return node, prefixLength, ip, nil } return 0, prefixLength, ip, newInvalidDatabaseError("invalid node in search tree") } func (r *Reader) traverseTree(ip net.IP, node, bitCount uint) (uint, int) { nodeCount := r.Metadata.NodeCount i := uint(0) for ; i < bitCount && node < nodeCount; i++ { bit := uint(1) & (uint(ip[i>>3]) >> (7 - (i % 8))) offset := node * r.nodeOffsetMult if bit == 0 { node = r.nodeReader.readLeft(offset) } else { node = r.nodeReader.readRight(offset) } } return node, int(i) } func (r *Reader) retrieveData(pointer uint, result interface{}) error { offset, err := r.resolveDataPointer(pointer) if err != nil { return err } return r.decode(offset, result) } func (r *Reader) resolveDataPointer(pointer uint) (uintptr, error) { resolved := uintptr(pointer - r.Metadata.NodeCount - dataSectionSeparatorSize) if resolved >= uintptr(len(r.buffer)) { return 0, newInvalidDatabaseError("the MaxMind DB file's search tree is corrupt") } return resolved, nil } maxminddb-golang-1.8.0/reader_appengine.go000066400000000000000000000015511371752156100205610ustar00rootroot00000000000000// +build appengine plan9 package maxminddb import "io/ioutil" // Open takes a string path to a MaxMind DB file and returns a Reader // structure or an error. The database file is opened using a memory map, // except on Google App Engine where mmap is not supported; there the database // is loaded into memory. Use the Close method on the Reader object to return // the resources to the system. func Open(file string) (*Reader, error) { bytes, err := ioutil.ReadFile(file) if err != nil { return nil, err } return FromBytes(bytes) } // Close unmaps the database file from virtual memory and returns the // resources to the system. If called on a Reader opened using FromBytes // or Open on Google App Engine, this method sets the underlying buffer // to nil, returning the resources to the system. func (r *Reader) Close() error { r.buffer = nil return nil } maxminddb-golang-1.8.0/reader_other.go000066400000000000000000000026311371752156100177340ustar00rootroot00000000000000// +build !appengine,!plan9 package maxminddb import ( "os" "runtime" ) // Open takes a string path to a MaxMind DB file and returns a Reader // structure or an error. The database file is opened using a memory map, // except on Google App Engine where mmap is not supported; there the database // is loaded into memory. Use the Close method on the Reader object to return // the resources to the system. func Open(file string) (*Reader, error) { mapFile, err := os.Open(file) if err != nil { _ = mapFile.Close() return nil, err } stats, err := mapFile.Stat() if err != nil { _ = mapFile.Close() return nil, err } fileSize := int(stats.Size()) mmap, err := mmap(int(mapFile.Fd()), fileSize) if err != nil { _ = mapFile.Close() return nil, err } if err := mapFile.Close(); err != nil { _ = munmap(mmap) return nil, err } reader, err := FromBytes(mmap) if err != nil { _ = munmap(mmap) return nil, err } reader.hasMappedFile = true runtime.SetFinalizer(reader, (*Reader).Close) return reader, nil } // Close unmaps the database file from virtual memory and returns the // resources to the system. If called on a Reader opened using FromBytes // or Open on Google App Engine, this method does nothing. func (r *Reader) Close() error { var err error if r.hasMappedFile { runtime.SetFinalizer(r, nil) r.hasMappedFile = false err = munmap(r.buffer) } r.buffer = nil return err } maxminddb-golang-1.8.0/reader_test.go000066400000000000000000000577201371752156100176030ustar00rootroot00000000000000package maxminddb import ( "errors" "fmt" "io/ioutil" "math/big" "math/rand" "net" "path/filepath" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestReader(t *testing.T) { for _, recordSize := range []uint{24, 28, 32} { for _, ipVersion := range []uint{4, 6} { fileName := fmt.Sprintf(testFile("MaxMind-DB-test-ipv%d-%d.mmdb"), ipVersion, recordSize) reader, err := Open(fileName) require.NoError(t, err, "unexpected error while opening database: %v", err) checkMetadata(t, reader, ipVersion, recordSize) if ipVersion == 4 { checkIpv4(t, reader) } else { checkIpv6(t, reader) } } } } func TestReaderBytes(t *testing.T) { for _, recordSize := range []uint{24, 28, 32} { for _, ipVersion := range []uint{4, 6} { fileName := fmt.Sprintf(testFile("MaxMind-DB-test-ipv%d-%d.mmdb"), ipVersion, recordSize) bytes, _ := ioutil.ReadFile(fileName) reader, err := FromBytes(bytes) require.NoError(t, err, "unexpected error while opening bytes: %v", err) checkMetadata(t, reader, ipVersion, recordSize) if ipVersion == 4 { checkIpv4(t, reader) } else { checkIpv6(t, reader) } } } } func TestLookupNetwork(t *testing.T) { bigInt := new(big.Int) bigInt.SetString("1329227995784915872903807060280344576", 10) decoderRecord := map[string]interface{}{ "array": []interface{}{ uint64(1), uint64(2), uint64(3), }, "boolean": true, "bytes": []uint8{ 0x0, 0x0, 0x0, 0x2a, }, "double": 42.123456, "float": float32(1.1), "int32": -268435456, "map": map[string]interface{}{ "mapX": map[string]interface{}{ "arrayX": []interface{}{ uint64(0x7), uint64(0x8), uint64(0x9), }, "utf8_stringX": "hello", }, }, "uint128": bigInt, "uint16": uint64(0x64), "uint32": uint64(0x10000000), "uint64": uint64(0x1000000000000000), "utf8_string": "unicode! ☯ - ♫", } tests := []struct { IP net.IP DBFile string ExpectedCIDR string ExpectedRecord interface{} ExpectedOK bool }{ { IP: net.ParseIP("1.1.1.1"), DBFile: "MaxMind-DB-test-ipv6-32.mmdb", ExpectedCIDR: "1.0.0.0/8", ExpectedRecord: nil, ExpectedOK: false, }, { IP: net.ParseIP("::1:ffff:ffff"), DBFile: "MaxMind-DB-test-ipv6-24.mmdb", ExpectedCIDR: "::1:ffff:ffff/128", ExpectedRecord: map[string]interface{}{"ip": "::1:ffff:ffff"}, ExpectedOK: true, }, { IP: net.ParseIP("::2:0:1"), DBFile: "MaxMind-DB-test-ipv6-24.mmdb", ExpectedCIDR: "::2:0:0/122", ExpectedRecord: map[string]interface{}{"ip": "::2:0:0"}, ExpectedOK: true, }, { IP: net.ParseIP("1.1.1.1"), DBFile: "MaxMind-DB-test-ipv4-24.mmdb", ExpectedCIDR: "1.1.1.1/32", ExpectedRecord: map[string]interface{}{"ip": "1.1.1.1"}, ExpectedOK: true, }, { IP: net.ParseIP("1.1.1.3"), DBFile: "MaxMind-DB-test-ipv4-24.mmdb", ExpectedCIDR: "1.1.1.2/31", ExpectedRecord: map[string]interface{}{"ip": "1.1.1.2"}, ExpectedOK: true, }, { IP: net.ParseIP("1.1.1.3"), DBFile: "MaxMind-DB-test-decoder.mmdb", ExpectedCIDR: "1.1.1.0/24", ExpectedRecord: decoderRecord, ExpectedOK: true, }, { IP: net.ParseIP("::ffff:1.1.1.128"), DBFile: "MaxMind-DB-test-decoder.mmdb", ExpectedCIDR: "1.1.1.0/24", ExpectedRecord: decoderRecord, ExpectedOK: true, }, { IP: net.ParseIP("::1.1.1.128"), DBFile: "MaxMind-DB-test-decoder.mmdb", ExpectedCIDR: "::101:100/120", ExpectedRecord: decoderRecord, ExpectedOK: true, }, { IP: net.ParseIP("200.0.2.1"), DBFile: "MaxMind-DB-no-ipv4-search-tree.mmdb", ExpectedCIDR: "::/64", ExpectedRecord: "::0/64", ExpectedOK: true, }, { IP: net.ParseIP("::200.0.2.1"), DBFile: "MaxMind-DB-no-ipv4-search-tree.mmdb", ExpectedCIDR: "::/64", ExpectedRecord: "::0/64", ExpectedOK: true, }, { IP: net.ParseIP("0:0:0:0:ffff:ffff:ffff:ffff"), DBFile: "MaxMind-DB-no-ipv4-search-tree.mmdb", ExpectedCIDR: "::/64", ExpectedRecord: "::0/64", ExpectedOK: true, }, { IP: net.ParseIP("ef00::"), DBFile: "MaxMind-DB-no-ipv4-search-tree.mmdb", ExpectedCIDR: "8000::/1", ExpectedRecord: nil, ExpectedOK: false, }, } for _, test := range tests { t.Run(fmt.Sprintf("%s - %s", test.DBFile, test.IP), func(t *testing.T) { var record interface{} reader, err := Open(testFile(test.DBFile)) require.NoError(t, err) network, ok, err := reader.LookupNetwork(test.IP, &record) require.NoError(t, err) assert.Equal(t, test.ExpectedOK, ok) assert.Equal(t, test.ExpectedCIDR, network.String()) assert.Equal(t, test.ExpectedRecord, record) }) } } func TestDecodingToInterface(t *testing.T) { reader, err := Open(testFile("MaxMind-DB-test-decoder.mmdb")) require.NoError(t, err, "unexpected error while opening database: %v", err) var recordInterface interface{} err = reader.Lookup(net.ParseIP("::1.1.1.0"), &recordInterface) require.NoError(t, err, "unexpected error while doing lookup: %v", err) checkDecodingToInterface(t, recordInterface) } func checkDecodingToInterface(t *testing.T, recordInterface interface{}) { record := recordInterface.(map[string]interface{}) assert.Equal(t, []interface{}{uint64(1), uint64(2), uint64(3)}, record["array"]) assert.Equal(t, true, record["boolean"]) assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x2a}, record["bytes"]) assert.Equal(t, 42.123456, record["double"]) assert.Equal(t, float32(1.1), record["float"]) assert.Equal(t, -268435456, record["int32"]) assert.Equal(t, map[string]interface{}{ "mapX": map[string]interface{}{ "arrayX": []interface{}{uint64(7), uint64(8), uint64(9)}, "utf8_stringX": "hello", }, }, record["map"], ) assert.Equal(t, uint64(100), record["uint16"]) assert.Equal(t, uint64(268435456), record["uint32"]) assert.Equal(t, uint64(1152921504606846976), record["uint64"]) assert.Equal(t, "unicode! ☯ - ♫", record["utf8_string"]) bigInt := new(big.Int) bigInt.SetString("1329227995784915872903807060280344576", 10) assert.Equal(t, bigInt, record["uint128"]) } // nolint: maligned type TestType struct { Array []uint `maxminddb:"array"` Boolean bool `maxminddb:"boolean"` Bytes []byte `maxminddb:"bytes"` Double float64 `maxminddb:"double"` Float float32 `maxminddb:"float"` Int32 int32 `maxminddb:"int32"` Map map[string]interface{} `maxminddb:"map"` Uint16 uint16 `maxminddb:"uint16"` Uint32 uint32 `maxminddb:"uint32"` Uint64 uint64 `maxminddb:"uint64"` Uint128 big.Int `maxminddb:"uint128"` Utf8String string `maxminddb:"utf8_string"` } func TestDecoder(t *testing.T) { reader, err := Open(testFile("MaxMind-DB-test-decoder.mmdb")) require.NoError(t, err) verify := func(result TestType) { assert.Equal(t, []uint{uint(1), uint(2), uint(3)}, result.Array) assert.Equal(t, true, result.Boolean) assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x2a}, result.Bytes) assert.Equal(t, 42.123456, result.Double) assert.Equal(t, float32(1.1), result.Float) assert.Equal(t, int32(-268435456), result.Int32) assert.Equal(t, map[string]interface{}{ "mapX": map[string]interface{}{ "arrayX": []interface{}{uint64(7), uint64(8), uint64(9)}, "utf8_stringX": "hello", }, }, result.Map, ) assert.Equal(t, uint16(100), result.Uint16) assert.Equal(t, uint32(268435456), result.Uint32) assert.Equal(t, uint64(1152921504606846976), result.Uint64) assert.Equal(t, "unicode! ☯ - ♫", result.Utf8String) bigInt := new(big.Int) bigInt.SetString("1329227995784915872903807060280344576", 10) assert.Equal(t, bigInt, &result.Uint128) } { // Directly lookup and decode. var result TestType require.NoError(t, reader.Lookup(net.ParseIP("::1.1.1.0"), &result)) verify(result) } { // Lookup record offset, then Decode. var result TestType offset, err := reader.LookupOffset(net.ParseIP("::1.1.1.0")) require.NoError(t, err) assert.NotEqual(t, NotFound, offset) assert.NoError(t, reader.Decode(offset, &result)) verify(result) } assert.NoError(t, reader.Close()) } type TestInterface interface { method() bool } func (t *TestType) method() bool { return t.Boolean } func TestStructInterface(t *testing.T) { var result TestInterface = &TestType{} reader, err := Open(testFile("MaxMind-DB-test-decoder.mmdb")) require.NoError(t, err) require.NoError(t, reader.Lookup(net.ParseIP("::1.1.1.0"), &result)) assert.Equal(t, true, result.method()) } func TestNonEmptyNilInterface(t *testing.T) { var result TestInterface reader, err := Open(testFile("MaxMind-DB-test-decoder.mmdb")) require.NoError(t, err) err = reader.Lookup(net.ParseIP("::1.1.1.0"), &result) assert.Equal(t, "maxminddb: cannot unmarshal map into type maxminddb.TestInterface", err.Error()) } type CityTraits struct { AutonomousSystemNumber uint `json:"autonomous_system_number,omitempty" maxminddb:"autonomous_system_number"` } type City struct { Traits CityTraits `maxminddb:"traits"` } func TestEmbeddedStructAsInterface(t *testing.T) { var city City var result interface{} = city.Traits db, err := Open(testFile("GeoIP2-ISP-Test.mmdb")) require.NoError(t, err) assert.NoError(t, db.Lookup(net.ParseIP("1.128.0.0"), &result)) } type BoolInterface interface { true() bool } type Bool bool func (b Bool) true() bool { return bool(b) } type ValueTypeTestType struct { Boolean BoolInterface `maxminddb:"boolean"` } func TestValueTypeInterface(t *testing.T) { var result ValueTypeTestType result.Boolean = Bool(false) reader, err := Open(testFile("MaxMind-DB-test-decoder.mmdb")) require.NoError(t, err) // although it would be nice to support cases like this, I am not sure it // is possible to do so in a general way. assert.Error(t, reader.Lookup(net.ParseIP("::1.1.1.0"), &result)) } type NestedMapX struct { UTF8StringX string `maxminddb:"utf8_stringX"` } type NestedPointerMapX struct { ArrayX []int `maxminddb:"arrayX"` } type PointerMap struct { MapX struct { Ignored string NestedMapX *NestedPointerMapX } `maxminddb:"mapX"` } type TestPointerType struct { Array *[]uint `maxminddb:"array"` Boolean *bool `maxminddb:"boolean"` Bytes *[]byte `maxminddb:"bytes"` Double *float64 `maxminddb:"double"` Float *float32 `maxminddb:"float"` Int32 *int32 `maxminddb:"int32"` Map *PointerMap `maxminddb:"map"` Uint16 *uint16 `maxminddb:"uint16"` Uint32 *uint32 `maxminddb:"uint32"` // Test for pointer to pointer Uint64 **uint64 `maxminddb:"uint64"` Uint128 *big.Int `maxminddb:"uint128"` Utf8String *string `maxminddb:"utf8_string"` } func TestComplexStructWithNestingAndPointer(t *testing.T) { reader, err := Open(testFile("MaxMind-DB-test-decoder.mmdb")) assert.NoError(t, err) var result TestPointerType err = reader.Lookup(net.ParseIP("::1.1.1.0"), &result) require.NoError(t, err) assert.Equal(t, []uint{uint(1), uint(2), uint(3)}, *result.Array) assert.Equal(t, true, *result.Boolean) assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x2a}, *result.Bytes) assert.Equal(t, 42.123456, *result.Double) assert.Equal(t, float32(1.1), *result.Float) assert.Equal(t, int32(-268435456), *result.Int32) assert.Equal(t, []int{7, 8, 9}, result.Map.MapX.ArrayX) assert.Equal(t, "hello", result.Map.MapX.UTF8StringX) assert.Equal(t, uint16(100), *result.Uint16) assert.Equal(t, uint32(268435456), *result.Uint32) assert.Equal(t, uint64(1152921504606846976), **result.Uint64) assert.Equal(t, "unicode! ☯ - ♫", *result.Utf8String) bigInt := new(big.Int) bigInt.SetString("1329227995784915872903807060280344576", 10) assert.Equal(t, bigInt, result.Uint128) assert.NoError(t, reader.Close()) } func TestNestedOffsetDecode(t *testing.T) { db, err := Open(testFile("GeoIP2-City-Test.mmdb")) require.NoError(t, err) off, err := db.LookupOffset(net.ParseIP("81.2.69.142")) assert.NotEqual(t, off, NotFound) require.NoError(t, err) var root struct { CountryOffset uintptr `maxminddb:"country"` Location struct { Latitude float64 `maxminddb:"latitude"` // Longitude is directly nested within the parent map. LongitudeOffset uintptr `maxminddb:"longitude"` // TimeZone is indirected via a pointer. TimeZoneOffset uintptr `maxminddb:"time_zone"` } `maxminddb:"location"` } assert.NoError(t, db.Decode(off, &root)) assert.Equal(t, 51.5142, root.Location.Latitude) var longitude float64 assert.NoError(t, db.Decode(root.Location.LongitudeOffset, &longitude)) assert.Equal(t, -0.0931, longitude) var timeZone string assert.NoError(t, db.Decode(root.Location.TimeZoneOffset, &timeZone)) assert.Equal(t, "Europe/London", timeZone) var country struct { IsoCode string `maxminddb:"iso_code"` } assert.NoError(t, db.Decode(root.CountryOffset, &country)) assert.Equal(t, "GB", country.IsoCode) assert.NoError(t, db.Close()) } func TestDecodingUint16IntoInt(t *testing.T) { reader, err := Open(testFile("MaxMind-DB-test-decoder.mmdb")) require.NoError(t, err, "unexpected error while opening database: %v", err) var result struct { Uint16 int `maxminddb:"uint16"` } err = reader.Lookup(net.ParseIP("::1.1.1.0"), &result) require.NoError(t, err) assert.Equal(t, 100, result.Uint16) } func TestIpv6inIpv4(t *testing.T) { reader, err := Open(testFile("MaxMind-DB-test-ipv4-24.mmdb")) require.NoError(t, err, "unexpected error while opening database: %v", err) var result TestType err = reader.Lookup(net.ParseIP("2001::"), &result) var emptyResult TestType assert.Equal(t, emptyResult, result) expected := errors.New("error looking up '2001::': you attempted to look up an IPv6 address in an IPv4-only database") assert.Equal(t, expected, err) assert.NoError(t, reader.Close(), "error on close") } func TestBrokenDoubleDatabase(t *testing.T) { reader, err := Open(testFile("GeoIP2-City-Test-Broken-Double-Format.mmdb")) require.NoError(t, err, "unexpected error while opening database: %v", err) var result interface{} err = reader.Lookup(net.ParseIP("2001:220::"), &result) expected := newInvalidDatabaseError("the MaxMind DB file's data section contains bad data (float 64 size of 2)") assert.Equal(t, expected, err) assert.NoError(t, reader.Close(), "error on close") } func TestInvalidNodeCountDatabase(t *testing.T) { _, err := Open(testFile("GeoIP2-City-Test-Invalid-Node-Count.mmdb")) expected := newInvalidDatabaseError("the MaxMind DB contains invalid metadata") assert.Equal(t, expected, err) } func TestMissingDatabase(t *testing.T) { reader, err := Open("file-does-not-exist.mmdb") assert.Nil(t, reader, "received reader when doing lookups on DB that doesn't exist") assert.Regexp(t, "open file-does-not-exist.mmdb.*", err) } func TestNonDatabase(t *testing.T) { reader, err := Open("README.md") assert.Nil(t, reader, "received reader when doing lookups on DB that doesn't exist") assert.Equal(t, "error opening database: invalid MaxMind DB file", err.Error()) } func TestDecodingToNonPointer(t *testing.T) { reader, _ := Open(testFile("MaxMind-DB-test-decoder.mmdb")) var recordInterface interface{} err := reader.Lookup(net.ParseIP("::1.1.1.0"), recordInterface) assert.Equal(t, "result param must be a pointer", err.Error()) assert.NoError(t, reader.Close(), "error on close") } func TestNilLookup(t *testing.T) { reader, _ := Open(testFile("MaxMind-DB-test-decoder.mmdb")) var recordInterface interface{} err := reader.Lookup(nil, recordInterface) assert.Equal(t, "IP passed to Lookup cannot be nil", err.Error()) assert.NoError(t, reader.Close(), "error on close") } func TestUsingClosedDatabase(t *testing.T) { reader, _ := Open(testFile("MaxMind-DB-test-decoder.mmdb")) require.NoError(t, reader.Close()) var recordInterface interface{} err := reader.Lookup(nil, recordInterface) assert.Equal(t, "cannot call Lookup on a closed database", err.Error()) _, err = reader.LookupOffset(nil) assert.Equal(t, "cannot call LookupOffset on a closed database", err.Error()) err = reader.Decode(0, recordInterface) assert.Equal(t, "cannot call Decode on a closed database", err.Error()) } func checkMetadata(t *testing.T, reader *Reader, ipVersion, recordSize uint) { metadata := reader.Metadata assert.Equal(t, uint(2), metadata.BinaryFormatMajorVersion) assert.Equal(t, uint(0), metadata.BinaryFormatMinorVersion) assert.IsType(t, uint(0), metadata.BuildEpoch) assert.Equal(t, "Test", metadata.DatabaseType) assert.Equal(t, metadata.Description, map[string]string{ "en": "Test Database", "zh": "Test Database Chinese", }) assert.Equal(t, ipVersion, metadata.IPVersion) assert.Equal(t, []string{"en", "zh"}, metadata.Languages) if ipVersion == 4 { assert.Equal(t, uint(164), metadata.NodeCount) } else { assert.Equal(t, uint(416), metadata.NodeCount) } assert.Equal(t, recordSize, metadata.RecordSize) } func checkIpv4(t *testing.T, reader *Reader) { for i := uint(0); i < 6; i++ { address := fmt.Sprintf("1.1.1.%d", uint(1)<> 24) ip[1] = byte(num >> 16) ip[2] = byte(num >> 8) ip[3] = byte(num) } func testFile(file string) string { return filepath.Join("test-data/test-data", file) } maxminddb-golang-1.8.0/test-data/000077500000000000000000000000001371752156100166265ustar00rootroot00000000000000maxminddb-golang-1.8.0/traverse.go000066400000000000000000000132401371752156100171220ustar00rootroot00000000000000package maxminddb import ( "fmt" "net" ) // Internal structure used to keep track of nodes we still need to visit. type netNode struct { ip net.IP bit uint pointer uint } // Networks represents a set of subnets that we are iterating over. type Networks struct { reader *Reader nodes []netNode // Nodes we still have to visit. lastNode netNode err error skipAliasedNetworks bool } var ( allIPv4 = &net.IPNet{IP: make(net.IP, 4), Mask: net.CIDRMask(0, 32)} allIPv6 = &net.IPNet{IP: make(net.IP, 16), Mask: net.CIDRMask(0, 128)} ) // NetworksOption are options for Networks and NetworksWithin type NetworksOption func(*Networks) // SkipAliasedNetworks is an option for Networks and NetworksWithin that // makes them not iterate over aliases of the IPv4 subtree in an IPv6 // database, e.g., ::ffff:0:0/96, 2001::/32, and 2002::/16. // // You most likely want to set this. The only reason it isn't the default // behavior is to provide backwards compatibility to existing users. func SkipAliasedNetworks(networks *Networks) { networks.skipAliasedNetworks = true } // Networks returns an iterator that can be used to traverse all networks in // the database. // // Please note that a MaxMind DB may map IPv4 networks into several locations // in an IPv6 database. This iterator will iterate over all of these locations // separately. To only iterate over the IPv4 networks once, use the // SkipAliasedNetworks option. func (r *Reader) Networks(options ...NetworksOption) *Networks { var networks *Networks if r.Metadata.IPVersion == 6 { networks = r.NetworksWithin(allIPv6, options...) } else { networks = r.NetworksWithin(allIPv4, options...) } return networks } // NetworksWithin returns an iterator that can be used to traverse all networks // in the database which are contained in a given network. // // Please note that a MaxMind DB may map IPv4 networks into several locations // in an IPv6 database. This iterator will iterate over all of these locations // separately. To only iterate over the IPv4 networks once, use the // SkipAliasedNetworks option. // // If the provided network is contained within a network in the database, the // iterator will iterate over exactly one network, the containing network. func (r *Reader) NetworksWithin(network *net.IPNet, options ...NetworksOption) *Networks { if r.Metadata.IPVersion == 4 && network.IP.To4() == nil { return &Networks{ err: fmt.Errorf( "error getting networks with '%s': you attempted to use an IPv6 network in an IPv4-only database", network.String(), ), } } networks := &Networks{reader: r} for _, option := range options { option(networks) } ip := network.IP prefixLength, _ := network.Mask.Size() if r.Metadata.IPVersion == 6 && len(ip) == net.IPv4len { if networks.skipAliasedNetworks { ip = net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ip[0], ip[1], ip[2], ip[3]} } else { ip = ip.To16() } prefixLength += 96 } pointer, bit := r.traverseTree(ip, 0, uint(prefixLength)) networks.nodes = []netNode{ { ip: ip, bit: uint(bit), pointer: pointer, }, } return networks } // Next prepares the next network for reading with the Network method. It // returns true if there is another network to be processed and false if there // are no more networks or if there is an error. func (n *Networks) Next() bool { if n.err != nil { return false } for len(n.nodes) > 0 { node := n.nodes[len(n.nodes)-1] n.nodes = n.nodes[:len(n.nodes)-1] for node.pointer != n.reader.Metadata.NodeCount { // This skips IPv4 aliases without hardcoding the networks that the writer // currently aliases. if n.skipAliasedNetworks && n.reader.ipv4Start != 0 && node.pointer == n.reader.ipv4Start && !isInIPv4Subtree(node.ip) { break } if node.pointer > n.reader.Metadata.NodeCount { n.lastNode = node return true } ipRight := make(net.IP, len(node.ip)) copy(ipRight, node.ip) if len(ipRight) <= int(node.bit>>3) { n.err = newInvalidDatabaseError( "invalid search tree at %v/%v", ipRight, node.bit) return false } ipRight[node.bit>>3] |= 1 << (7 - (node.bit % 8)) offset := node.pointer * n.reader.nodeOffsetMult rightPointer := n.reader.nodeReader.readRight(offset) node.bit++ n.nodes = append(n.nodes, netNode{ pointer: rightPointer, ip: ipRight, bit: node.bit, }) node.pointer = n.reader.nodeReader.readLeft(offset) } } return false } // Network returns the current network or an error if there is a problem // decoding the data for the network. It takes a pointer to a result value to // decode the network's data into. func (n *Networks) Network(result interface{}) (*net.IPNet, error) { if n.err != nil { return nil, n.err } if err := n.reader.retrieveData(n.lastNode.pointer, result); err != nil { return nil, err } ip := n.lastNode.ip prefixLength := int(n.lastNode.bit) // We do this because uses of SkipAliasedNetworks expect the IPv4 networks // to be returned as IPv4 networks. If we are not skipping aliased // networks, then the user will get IPv4 networks from the ::FFFF:0:0/96 // network as Go automatically converts those. if n.skipAliasedNetworks && isInIPv4Subtree(ip) { ip = ip[12:] prefixLength -= 96 } return &net.IPNet{ IP: ip, Mask: net.CIDRMask(prefixLength, len(ip)*8), }, nil } // Err returns an error, if any, that was encountered during iteration. func (n *Networks) Err() error { return n.err } // isInIPv4Subtree returns true if the IP is an IPv6 address in the database's // IPv4 subtree. func isInIPv4Subtree(ip net.IP) bool { if len(ip) != 16 { return false } for i := 0; i < 12; i++ { if ip[i] != 0 { return false } } return true } maxminddb-golang-1.8.0/traverse_test.go000066400000000000000000000137241371752156100201700ustar00rootroot00000000000000package maxminddb import ( "fmt" "net" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestNetworks(t *testing.T) { for _, recordSize := range []uint{24, 28, 32} { for _, ipVersion := range []uint{4, 6} { fileName := testFile(fmt.Sprintf("MaxMind-DB-test-ipv%d-%d.mmdb", ipVersion, recordSize)) reader, err := Open(fileName) require.Nil(t, err, "unexpected error while opening database: %v", err) n := reader.Networks() for n.Next() { record := struct { IP string `maxminddb:"ip"` }{} network, err := n.Network(&record) assert.Nil(t, err) assert.Equal(t, record.IP, network.IP.String(), "expected %s got %s", record.IP, network.IP.String(), ) } assert.Nil(t, n.Err()) assert.NoError(t, reader.Close()) } } } func TestNetworksWithInvalidSearchTree(t *testing.T) { reader, err := Open(testFile("MaxMind-DB-test-broken-search-tree-24.mmdb")) require.Nil(t, err, "unexpected error while opening database: %v", err) n := reader.Networks() for n.Next() { var record interface{} _, err := n.Network(&record) assert.Nil(t, err) } assert.NotNil(t, n.Err(), "no error received when traversing an broken search tree") assert.Equal(t, "invalid search tree at 128.128.128.128/32", n.Err().Error()) assert.NoError(t, reader.Close()) } type networkTest struct { Network string Database string Expected []string Options []NetworksOption } var tests = []networkTest{ { Network: "0.0.0.0/0", Database: "ipv4", Expected: []string{ "1.1.1.1/32", "1.1.1.2/31", "1.1.1.4/30", "1.1.1.8/29", "1.1.1.16/28", "1.1.1.32/32", }, }, { Network: "1.1.1.1/30", Database: "ipv4", Expected: []string{ "1.1.1.1/32", "1.1.1.2/31", }, }, { Network: "1.1.1.1/32", Database: "ipv4", Expected: []string{ "1.1.1.1/32", }, }, { Network: "255.255.255.0/24", Database: "ipv4", Expected: []string(nil), }, { Network: "1.1.1.1/32", Database: "mixed", Expected: []string{ "1.1.1.1/32", }, }, { Network: "255.255.255.0/24", Database: "mixed", Expected: []string(nil), }, { Network: "::1:ffff:ffff/128", Database: "ipv6", Expected: []string{ "::1:ffff:ffff/128", }, Options: []NetworksOption{SkipAliasedNetworks}, }, { Network: "::/0", Database: "ipv6", Expected: []string{ "::1:ffff:ffff/128", "::2:0:0/122", "::2:0:40/124", "::2:0:50/125", "::2:0:58/127", }, Options: []NetworksOption{SkipAliasedNetworks}, }, { Network: "::2:0:40/123", Database: "ipv6", Expected: []string{ "::2:0:40/124", "::2:0:50/125", "::2:0:58/127", }, Options: []NetworksOption{SkipAliasedNetworks}, }, { Network: "0:0:0:0:0:ffff:ffff:ff00/120", Database: "ipv6", Expected: []string(nil), }, { Network: "0.0.0.0/0", Database: "mixed", Expected: []string{ "1.1.1.1/32", "1.1.1.2/31", "1.1.1.4/30", "1.1.1.8/29", "1.1.1.16/28", "1.1.1.32/32", }, }, { Network: "0.0.0.0/0", Database: "mixed", Expected: []string{ "1.1.1.1/32", "1.1.1.2/31", "1.1.1.4/30", "1.1.1.8/29", "1.1.1.16/28", "1.1.1.32/32", }, Options: []NetworksOption{SkipAliasedNetworks}, }, { Network: "::/0", Database: "mixed", Expected: []string{ "::101:101/128", "::101:102/127", "::101:104/126", "::101:108/125", "::101:110/124", "::101:120/128", "::1:ffff:ffff/128", "::2:0:0/122", "::2:0:40/124", "::2:0:50/125", "::2:0:58/127", "1.1.1.1/32", "1.1.1.2/31", "1.1.1.4/30", "1.1.1.8/29", "1.1.1.16/28", "1.1.1.32/32", "2001:0:101:101::/64", "2001:0:101:102::/63", "2001:0:101:104::/62", "2001:0:101:108::/61", "2001:0:101:110::/60", "2001:0:101:120::/64", "2002:101:101::/48", "2002:101:102::/47", "2002:101:104::/46", "2002:101:108::/45", "2002:101:110::/44", "2002:101:120::/48", }, }, { Network: "::/0", Database: "mixed", Expected: []string{ "1.1.1.1/32", "1.1.1.2/31", "1.1.1.4/30", "1.1.1.8/29", "1.1.1.16/28", "1.1.1.32/32", "::1:ffff:ffff/128", "::2:0:0/122", "::2:0:40/124", "::2:0:50/125", "::2:0:58/127", }, Options: []NetworksOption{SkipAliasedNetworks}, }, { Network: "1.1.1.16/28", Database: "mixed", Expected: []string{ "1.1.1.16/28", }, }, { Network: "1.1.1.4/30", Database: "ipv4", Expected: []string{ "1.1.1.4/30", }, }, } func TestNetworksWithin(t *testing.T) { for _, v := range tests { for _, recordSize := range []uint{24, 28, 32} { fileName := testFile(fmt.Sprintf("MaxMind-DB-test-%s-%d.mmdb", v.Database, recordSize)) reader, err := Open(fileName) require.Nil(t, err, "unexpected error while opening database: %v", err) _, network, err := net.ParseCIDR(v.Network) assert.Nil(t, err) n := reader.NetworksWithin(network, v.Options...) var innerIPs []string for n.Next() { record := struct { IP string `maxminddb:"ip"` }{} network, err := n.Network(&record) assert.Nil(t, err) innerIPs = append(innerIPs, network.String()) } assert.Equal(t, v.Expected, innerIPs) assert.Nil(t, n.Err()) assert.NoError(t, reader.Close()) } } } var geoIPTests = []networkTest{ { Network: "81.2.69.128/26", Database: "GeoIP2-Country-Test.mmdb", Expected: []string{ "81.2.69.142/31", "81.2.69.144/28", "81.2.69.160/27", }, }, } func TestGeoIPNetworksWithin(t *testing.T) { for _, v := range geoIPTests { fileName := testFile(v.Database) reader, err := Open(fileName) require.Nil(t, err, "unexpected error while opening database: %v", err) _, network, err := net.ParseCIDR(v.Network) assert.Nil(t, err) n := reader.NetworksWithin(network) var innerIPs []string for n.Next() { record := struct { IP string `maxminddb:"ip"` }{} network, err := n.Network(&record) assert.Nil(t, err) innerIPs = append(innerIPs, network.String()) } assert.Equal(t, v.Expected, innerIPs) assert.Nil(t, n.Err()) assert.NoError(t, reader.Close()) } } maxminddb-golang-1.8.0/verifier.go000066400000000000000000000075601371752156100171120ustar00rootroot00000000000000package maxminddb import ( "reflect" "runtime" ) type verifier struct { reader *Reader } // Verify checks that the database is valid. It validates the search tree, // the data section, and the metadata section. This verifier is stricter than // the specification and may return errors on databases that are readable. func (r *Reader) Verify() error { v := verifier{r} if err := v.verifyMetadata(); err != nil { return err } err := v.verifyDatabase() runtime.KeepAlive(v.reader) return err } func (v *verifier) verifyMetadata() error { metadata := v.reader.Metadata if metadata.BinaryFormatMajorVersion != 2 { return testError( "binary_format_major_version", 2, metadata.BinaryFormatMajorVersion, ) } if metadata.BinaryFormatMinorVersion != 0 { return testError( "binary_format_minor_version", 0, metadata.BinaryFormatMinorVersion, ) } if metadata.DatabaseType == "" { return testError( "database_type", "non-empty string", metadata.DatabaseType, ) } if len(metadata.Description) == 0 { return testError( "description", "non-empty slice", metadata.Description, ) } if metadata.IPVersion != 4 && metadata.IPVersion != 6 { return testError( "ip_version", "4 or 6", metadata.IPVersion, ) } if metadata.RecordSize != 24 && metadata.RecordSize != 28 && metadata.RecordSize != 32 { return testError( "record_size", "24, 28, or 32", metadata.RecordSize, ) } if metadata.NodeCount == 0 { return testError( "node_count", "positive integer", metadata.NodeCount, ) } return nil } func (v *verifier) verifyDatabase() error { offsets, err := v.verifySearchTree() if err != nil { return err } if err := v.verifyDataSectionSeparator(); err != nil { return err } return v.verifyDataSection(offsets) } func (v *verifier) verifySearchTree() (map[uint]bool, error) { offsets := make(map[uint]bool) it := v.reader.Networks() for it.Next() { offset, err := v.reader.resolveDataPointer(it.lastNode.pointer) if err != nil { return nil, err } offsets[uint(offset)] = true } if err := it.Err(); err != nil { return nil, err } return offsets, nil } func (v *verifier) verifyDataSectionSeparator() error { separatorStart := v.reader.Metadata.NodeCount * v.reader.Metadata.RecordSize / 4 separator := v.reader.buffer[separatorStart : separatorStart+dataSectionSeparatorSize] for _, b := range separator { if b != 0 { return newInvalidDatabaseError("unexpected byte in data separator: %v", separator) } } return nil } func (v *verifier) verifyDataSection(offsets map[uint]bool) error { pointerCount := len(offsets) decoder := v.reader.decoder var offset uint bufferLen := uint(len(decoder.buffer)) for offset < bufferLen { var data interface{} rv := reflect.ValueOf(&data) newOffset, err := decoder.decode(offset, rv, 0) if err != nil { return newInvalidDatabaseError("received decoding error (%v) at offset of %v", err, offset) } if newOffset <= offset { return newInvalidDatabaseError("data section offset unexpectedly went from %v to %v", offset, newOffset) } pointer := offset if _, ok := offsets[pointer]; ok { delete(offsets, pointer) } else { return newInvalidDatabaseError("found data (%v) at %v that the search tree does not point to", data, pointer) } offset = newOffset } if offset != bufferLen { return newInvalidDatabaseError( "unexpected data at the end of the data section (last offset: %v, end: %v)", offset, bufferLen, ) } if len(offsets) != 0 { return newInvalidDatabaseError( "found %v pointers (of %v) in the search tree that we did not see in the data section", len(offsets), pointerCount, ) } return nil } func testError( field string, expected interface{}, actual interface{}, ) error { return newInvalidDatabaseError( "%v - Expected: %v Actual: %v", field, expected, actual, ) } maxminddb-golang-1.8.0/verifier_test.go000066400000000000000000000030341371752156100201410ustar00rootroot00000000000000package maxminddb import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestVerifyOnGoodDatabases(t *testing.T) { databases := []string{ "GeoIP2-Anonymous-IP-Test.mmdb", "GeoIP2-City-Test.mmdb", "GeoIP2-Connection-Type-Test.mmdb", "GeoIP2-Country-Test.mmdb", "GeoIP2-Domain-Test.mmdb", "GeoIP2-ISP-Test.mmdb", "GeoIP2-Precision-Enterprise-Test.mmdb", "MaxMind-DB-no-ipv4-search-tree.mmdb", "MaxMind-DB-string-value-entries.mmdb", "MaxMind-DB-test-decoder.mmdb", "MaxMind-DB-test-ipv4-24.mmdb", "MaxMind-DB-test-ipv4-28.mmdb", "MaxMind-DB-test-ipv4-32.mmdb", "MaxMind-DB-test-ipv6-24.mmdb", "MaxMind-DB-test-ipv6-28.mmdb", "MaxMind-DB-test-ipv6-32.mmdb", "MaxMind-DB-test-mixed-24.mmdb", "MaxMind-DB-test-mixed-28.mmdb", "MaxMind-DB-test-mixed-32.mmdb", "MaxMind-DB-test-nested.mmdb", } for _, database := range databases { t.Run(database, func(t *testing.T) { reader, err := Open(testFile(database)) require.NoError(t, err) assert.NoError(t, reader.Verify(), "Received error (%v) when verifying %v", err, database) }) } } func TestVerifyOnBrokenDatabases(t *testing.T) { databases := []string{ "GeoIP2-City-Test-Broken-Double-Format.mmdb", "MaxMind-DB-test-broken-pointers-24.mmdb", "MaxMind-DB-test-broken-search-tree-24.mmdb", } for _, database := range databases { reader, err := Open(testFile(database)) assert.Nil(t, err) assert.NotNil(t, reader.Verify(), "Did not receive expected error when verifying %v", database, ) } }