pax_global_header00006660000000000000000000000064131023555730014516gustar00rootroot0000000000000052 comment=d19f6d453e836d12ee8fe895d0494421e93ef8c1 maxminddb-golang-1.2.0/000077500000000000000000000000001310235557300147265ustar00rootroot00000000000000maxminddb-golang-1.2.0/.gitignore000066400000000000000000000000331310235557300167120ustar00rootroot00000000000000.vscode *.out *.sw? *.test maxminddb-golang-1.2.0/.gitmodules000066400000000000000000000001311310235557300170760ustar00rootroot00000000000000[submodule "test-data"] path = test-data url = git://github.com/maxmind/MaxMind-DB.git maxminddb-golang-1.2.0/.travis.yml000066400000000000000000000006361310235557300170440ustar00rootroot00000000000000language: go go: - 1.4 - 1.5 - 1.6 - 1.7 - 1.8 - tip before_install: - "if [[ $TRAVIS_GO_VERSION == 1.7 ]]; then go get -v github.com/golang/lint/golint; fi" install: - go get -v -t ./... script: - go test -race -cpu 1,4 -v - go test -race -v -tags appengine - "if [[ $TRAVIS_GO_VERSION == 1.7 ]]; then go vet ./...; fi" - "if [[ $TRAVIS_GO_VERSION == 1.7 ]]; then golint .; fi" sudo: false maxminddb-golang-1.2.0/LICENSE000066400000000000000000000014041310235557300157320ustar00rootroot00000000000000ISC 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.2.0/README.md000066400000000000000000000024411310235557300162060ustar00rootroot00000000000000# MaxMind DB Reader for Go # [![Build Status](https://travis-ci.org/oschwald/maxminddb-golang.png?branch=master)](https://travis-ci.org/oschwald/maxminddb-golang) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/4j2f9oep8nnfrmov/branch/master?svg=true)](https://ci.appveyor.com/project/oschwald/maxminddb-golang/branch/master) [![GoDoc](https://godoc.org/github.com/oschwald/maxminddb-golang?status.png)](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.2.0/appveyor.yml000066400000000000000000000005001310235557300173110ustar00rootroot00000000000000version: "{build}" os: Windows Server 2012 R2 clone_folder: c:\gopath\src\github.com\oschwald\maxminddb-golang environment: GOPATH: c:\gopath install: - echo %PATH% - echo %GOPATH% - git submodule update --init --recursive - go version - go env - go get -v -t ./... build_script: - go test -v ./... maxminddb-golang-1.2.0/decoder.go000066400000000000000000000427371310235557300166770ustar00rootroot00000000000000package 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 _Container _Marker _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) 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) unmarshalBool(size uint, 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, err := d.decodeBool(size, offset) if err != nil { return 0, err } 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 uint, offset uint, result reflect.Value) (uint, error) { value, newOffset, err := d.decodeBytes(size, offset) if err != nil { return 0, err } 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 uint, 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, err := d.decodeFloat32(size, offset) if err != nil { return 0, err } 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 uint, 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, err := d.decodeFloat64(size, offset) if err != nil { return 0, err } 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 uint, 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, err := d.decodeInt(size, offset) if err != nil { return 0, err } 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 uint, 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 uint, offset uint, result reflect.Value) (uint, error) { value, newOffset, err := d.decodeString(size, offset) if err != nil { return 0, err } 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 uint, 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, err := d.decodeUint(size, offset) if err != nil { return 0, err } 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 uint, 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, err := d.decodeUint128(size, offset) if err != nil { return 0, err } 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 uint, offset uint) (bool, uint, error) { return size != 0, offset, nil } func (d *decoder) decodeBytes(size uint, offset uint) ([]byte, uint, error) { newOffset := offset + size bytes := make([]byte, size) copy(bytes, d.buffer[offset:newOffset]) return bytes, newOffset, nil } func (d *decoder) decodeFloat64(size uint, offset uint) (float64, uint, error) { newOffset := offset + size bits := binary.BigEndian.Uint64(d.buffer[offset:newOffset]) return math.Float64frombits(bits), newOffset, nil } func (d *decoder) decodeFloat32(size uint, offset uint) (float32, uint, error) { newOffset := offset + size bits := binary.BigEndian.Uint32(d.buffer[offset:newOffset]) return math.Float32frombits(bits), newOffset, nil } func (d *decoder) decodeInt(size uint, offset uint) (int, uint, error) { newOffset := offset + size var val int32 for _, b := range d.buffer[offset:newOffset] { val = (val << 8) | int32(b) } return int(val), newOffset, nil } func (d *decoder) decodeMap( size uint, offset uint, result reflect.Value, depth int, ) (uint, error) { if result.IsNil() { result.Set(reflect.MakeMap(result.Type())) } 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 } value := reflect.New(result.Type().Elem()) offset, err = d.decode(offset, value, depth) if err != nil { return 0, err } result.SetMapIndex(reflect.ValueOf(string(key)), value.Elem()) } 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 = uint(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) decodeString(size uint, offset uint) (string, uint, error) { newOffset := offset + size return string(d.buffer[offset:newOffset]), newOffset, nil } type fieldsType struct { namedFields map[string]int anonymousFields []int } var ( fieldMap = map[reflect.Type]*fieldsType{} fieldMapMu sync.RWMutex ) func (d *decoder) decodeStruct( size uint, offset uint, result reflect.Value, depth int, ) (uint, error) { resultType := result.Type() fieldMapMu.RLock() fields, ok := fieldMap[resultType] fieldMapMu.RUnlock() if !ok { 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 } fieldMapMu.Lock() fields = &fieldsType{namedFields, anonymous} fieldMap[resultType] = fields fieldMapMu.Unlock() } // 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 } func (d *decoder) decodeUint(size uint, offset uint) (uint64, uint, error) { newOffset := offset + size bytes := d.buffer[offset:newOffset] var val uint64 for _, b := range bytes { val = (val << 8) | uint64(b) } return val, newOffset, nil } func (d *decoder) decodeUint128(size uint, offset uint) (*big.Int, uint, error) { newOffset := offset + size val := new(big.Int) val.SetBytes(d.buffer[offset:newOffset]) return val, newOffset, nil } 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 uint, 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.2.0/decoder_test.go000066400000000000000000000144661310235557300177340ustar00rootroot00000000000000package maxminddb import ( "encoding/hex" "io/ioutil" "math/big" "reflect" "strings" "testing" "github.com/stretchr/testify/assert" ) 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.Nil(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("test-data/test-data/maps-with-pointers.raw") assert.Nil(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.Nil(t, err) if !reflect.DeepEqual(actual, expectedValue) { t.Errorf("Decode for pointer at %d failed", offset) } } } maxminddb-golang-1.2.0/errors.go000066400000000000000000000021711310235557300165720ustar00rootroot00000000000000package 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.2.0/example_test.go000066400000000000000000000074131310235557300177540ustar00rootroot00000000000000package 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() 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: // ::100:0/120: Dialup // ::100:100/120: Cable/DSL // ::100:200/119: Dialup // ::100:400/118: Dialup // ::100:800/117: Dialup // ::100:1000/116: Dialup // ::100:2000/115: Dialup // ::100:4000/114: Dialup // ::100:8000/113: Dialup // ::50d6:0/116: Cellular // ::6001:0/112: Cable/DSL // ::600a:0/111: Cable/DSL // ::6045:0/112: Cable/DSL // ::605e:0/111: Cable/DSL // ::6c60:0/107: Cellular // ::af10:c700/120: Dialup // ::bb9c:8a00/120: Cable/DSL // ::c9f3:c800/120: Corporate // ::cfb3:3000/116: Cellular // 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 // 2001:0:100::/56: Dialup // 2001:0:100:100::/56: Cable/DSL // 2001:0:100:200::/55: Dialup // 2001:0:100:400::/54: Dialup // 2001:0:100:800::/53: Dialup // 2001:0:100:1000::/52: Dialup // 2001:0:100:2000::/51: Dialup // 2001:0:100:4000::/50: Dialup // 2001:0:100:8000::/49: Dialup // 2001:0:50d6::/52: Cellular // 2001:0:6001::/48: Cable/DSL // 2001:0:600a::/47: Cable/DSL // 2001:0:6045::/48: Cable/DSL // 2001:0:605e::/47: Cable/DSL // 2001:0:6c60::/43: Cellular // 2001:0:af10:c700::/56: Dialup // 2001:0:bb9c:8a00::/56: Cable/DSL // 2001:0:c9f3:c800::/56: Corporate // 2001:0:cfb3:3000::/52: Cellular // 2002:100::/40: Dialup // 2002:100:100::/40: Cable/DSL // 2002:100:200::/39: Dialup // 2002:100:400::/38: Dialup // 2002:100:800::/37: Dialup // 2002:100:1000::/36: Dialup // 2002:100:2000::/35: Dialup // 2002:100:4000::/34: Dialup // 2002:100:8000::/33: Dialup // 2002:50d6::/36: Cellular // 2002:6001::/32: Cable/DSL // 2002:600a::/31: Cable/DSL // 2002:6045::/32: Cable/DSL // 2002:605e::/31: Cable/DSL // 2002:6c60::/27: Cellular // 2002:af10:c700::/40: Dialup // 2002:bb9c:8a00::/40: Cable/DSL // 2002:c9f3:c800::/40: Corporate // 2002:cfb3:3000::/36: Cellular // 2003::/24: Cable/DSL } maxminddb-golang-1.2.0/mmap_unix.go000066400000000000000000000004441310235557300172540ustar00rootroot00000000000000// +build !windows,!appengine package maxminddb import ( "syscall" "golang.org/x/sys/unix" ) func mmap(fd int, length int) (data []byte, err error) { return unix.Mmap(fd, 0, length, syscall.PROT_READ, syscall.MAP_SHARED) } func munmap(b []byte) (err error) { return unix.Munmap(b) } maxminddb-golang-1.2.0/mmap_windows.go000066400000000000000000000034221310235557300177620ustar00rootroot00000000000000// +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.2.0/reader.go000066400000000000000000000165001310235557300165210ustar00rootroot00000000000000package 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. type Reader struct { hasMappedFile bool buffer []byte decoder decoder Metadata Metadata ipv4Start uint } // Metadata holds the metadata decoded from the MaxMind DB file. In particular // in 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)], } reader := &Reader{ buffer: buffer, decoder: d, Metadata: metadata, ipv4Start: 0, } reader.ipv4Start, err = reader.startNode() return reader, err } func (r *Reader) startNode() (uint, error) { if r.Metadata.IPVersion != 6 { return 0, nil } nodeCount := r.Metadata.NodeCount node := uint(0) var err error for i := 0; i < 96 && node < nodeCount; i++ { node, err = r.readNode(node, 0) if err != nil { return 0, err } } return node, err } // Lookup takes an IP address as a net.IP structure and a pointer to the // result value to Decode into. func (r *Reader) Lookup(ipAddress net.IP, result interface{}) error { pointer, err := r.lookupPointer(ipAddress) if pointer == 0 || err != nil { return err } return 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(ipAddress net.IP) (uintptr, error) { pointer, err := r.lookupPointer(ipAddress) if pointer == 0 || err != nil { return NotFound, err } return r.resolveDataPointer(pointer) } // 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 { rv := reflect.ValueOf(result) if rv.Kind() != reflect.Ptr || rv.IsNil() { return errors.New("result param must be a pointer") } _, err := r.decoder.decode(uint(offset), reflect.ValueOf(result), 0) return err } func (r *Reader) lookupPointer(ipAddress net.IP) (uint, error) { if ipAddress == nil { return 0, errors.New("ipAddress passed to Lookup cannot be nil") } ipV4Address := ipAddress.To4() if ipV4Address != nil { ipAddress = ipV4Address } if len(ipAddress) == 16 && r.Metadata.IPVersion == 4 { return 0, fmt.Errorf("error looking up '%s': you attempted to look up an IPv6 address in an IPv4-only database", ipAddress.String()) } return r.findAddressInTree(ipAddress) } func (r *Reader) findAddressInTree(ipAddress net.IP) (uint, error) { bitCount := uint(len(ipAddress) * 8) var node uint if bitCount == 32 { node = r.ipv4Start } nodeCount := r.Metadata.NodeCount for i := uint(0); i < bitCount && node < nodeCount; i++ { bit := uint(1) & (uint(ipAddress[i>>3]) >> (7 - (i % 8))) var err error node, err = r.readNode(node, bit) if err != nil { return 0, err } } if node == nodeCount { // Record is empty return 0, nil } else if node > nodeCount { return node, nil } return 0, newInvalidDatabaseError("invalid node in search tree") } func (r *Reader) readNode(nodeNumber uint, index uint) (uint, error) { RecordSize := r.Metadata.RecordSize baseOffset := nodeNumber * RecordSize / 4 var nodeBytes []byte var prefix uint switch RecordSize { case 24: offset := baseOffset + index*3 nodeBytes = r.buffer[offset : offset+3] case 28: prefix = uint(r.buffer[baseOffset+3]) if index != 0 { prefix &= 0x0F } else { prefix = (0xF0 & prefix) >> 4 } offset := baseOffset + index*4 nodeBytes = r.buffer[offset : offset+3] case 32: offset := baseOffset + index*4 nodeBytes = r.buffer[offset : offset+4] default: return 0, newInvalidDatabaseError("unknown record size: %d", RecordSize) } return uintFromBytes(prefix, nodeBytes), nil } 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) { var 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.2.0/reader_appengine.go000066400000000000000000000014241310235557300205460ustar00rootroot00000000000000// +build appengine 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 does nothing. func (r *Reader) Close() error { return nil } maxminddb-golang-1.2.0/reader_other.go000066400000000000000000000026421310235557300177240ustar00rootroot00000000000000// +build !appengine 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 { return nil, err } defer func() { if rerr := mapFile.Close(); rerr != nil { err = rerr } }() stats, err := mapFile.Stat() if err != nil { return nil, err } fileSize := int(stats.Size()) mmap, err := mmap(int(mapFile.Fd()), fileSize) if err != nil { return nil, err } reader, err := FromBytes(mmap) if err != nil { if err2 := munmap(mmap); err2 != nil { // failing to unmap the file is probably the more severe error return nil, err2 } return nil, err } reader.hasMappedFile = true runtime.SetFinalizer(reader, (*Reader).Close) return reader, err } // 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 { if !r.hasMappedFile { return nil } runtime.SetFinalizer(r, nil) r.hasMappedFile = false return munmap(r.buffer) } maxminddb-golang-1.2.0/reader_test.go000066400000000000000000000375131310235557300175670ustar00rootroot00000000000000package maxminddb import ( "errors" "fmt" "io/ioutil" "math/big" "math/rand" "net" "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("test-data/test-data/MaxMind-DB-test-ipv%d-%d.mmdb", ipVersion, recordSize) reader, err := Open(fileName) require.Nil(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("test-data/test-data/MaxMind-DB-test-ipv%d-%d.mmdb", ipVersion, recordSize) bytes, _ := ioutil.ReadFile(fileName) reader, err := FromBytes(bytes) require.Nil(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 TestDecodingToInterface(t *testing.T) { reader, err := Open("test-data/test-data/MaxMind-DB-test-decoder.mmdb") assert.Nil(t, err, "unexpected error while opening database: %v", err) var recordInterface interface{} err = reader.Lookup(net.ParseIP("::1.1.1.0"), &recordInterface) require.Nil(t, err, "unexpected error while doing lookup: %v", err) record := recordInterface.(map[string]interface{}) assert.Equal(t, record["array"], []interface{}{uint64(1), uint64(2), uint64(3)}) assert.Equal(t, record["boolean"], true) assert.Equal(t, record["bytes"], []byte{0x00, 0x00, 0x00, 0x2a}) assert.Equal(t, record["double"], 42.123456) assert.Equal(t, record["float"], float32(1.1)) assert.Equal(t, record["int32"], -268435456) assert.Equal(t, record["map"], map[string]interface{}{ "mapX": map[string]interface{}{ "arrayX": []interface{}{uint64(7), uint64(8), uint64(9)}, "utf8_stringX": "hello", }}) assert.Equal(t, record["uint16"], uint64(100)) assert.Equal(t, record["uint32"], uint64(268435456)) assert.Equal(t, record["uint64"], uint64(1152921504606846976)) assert.Equal(t, record["utf8_string"], "unicode! ☯ - ♫") bigInt := new(big.Int) bigInt.SetString("1329227995784915872903807060280344576", 10) assert.Equal(t, record["uint128"], bigInt) } 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("test-data/test-data/MaxMind-DB-test-decoder.mmdb") require.Nil(t, err) verify := func(result TestType) { assert.Equal(t, result.Array, []uint{uint(1), uint(2), uint(3)}) assert.Equal(t, result.Boolean, true) assert.Equal(t, result.Bytes, []byte{0x00, 0x00, 0x00, 0x2a}) assert.Equal(t, result.Double, 42.123456) assert.Equal(t, result.Float, float32(1.1)) assert.Equal(t, result.Int32, int32(-268435456)) assert.Equal(t, result.Map, map[string]interface{}{ "mapX": map[string]interface{}{ "arrayX": []interface{}{uint64(7), uint64(8), uint64(9)}, "utf8_stringX": "hello", }}) assert.Equal(t, result.Uint16, uint16(100)) assert.Equal(t, result.Uint32, uint32(268435456)) assert.Equal(t, result.Uint64, uint64(1152921504606846976)) assert.Equal(t, result.Utf8String, "unicode! ☯ - ♫") bigInt := new(big.Int) bigInt.SetString("1329227995784915872903807060280344576", 10) assert.Equal(t, &result.Uint128, bigInt) } { // Directly lookup and decode. var result TestType assert.Nil(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")) assert.Nil(t, err) assert.NotEqual(t, offset, NotFound) assert.Nil(t, reader.Decode(offset, &result)) verify(result) } assert.Nil(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("test-data/test-data/MaxMind-DB-test-decoder.mmdb") require.Nil(t, err) require.Nil(t, reader.Lookup(net.ParseIP("::1.1.1.0"), &result)) assert.Equal(t, result.method(), true) } func TestNonEmptyNilInterface(t *testing.T) { var result TestInterface reader, err := Open("test-data/test-data/MaxMind-DB-test-decoder.mmdb") require.Nil(t, err) err = reader.Lookup(net.ParseIP("::1.1.1.0"), &result) assert.Equal(t, err.Error(), "maxminddb: cannot unmarshal map into type maxminddb.TestInterface") } 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("test-data/test-data/GeoIP2-ISP-Test.mmdb") require.Nil(t, err) assert.Nil(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 TesValueTypeInterface(t *testing.T) { var result ValueTypeTestType result.Boolean = Bool(false) reader, err := Open("test-data/test-data/MaxMind-DB-test-decoder.mmdb") require.Nil(t, err) require.Nil(t, reader.Lookup(net.ParseIP("::1.1.1.0"), &result)) assert.Equal(t, result.Boolean.true(), true) } type NestedMapX struct { UTF8StringX string `maxminddb:"utf8_stringX"` } type NestedPointerMapX struct { ArrayX []int `maxminddb:"arrayX"` } type PointerMap struct { MapX struct { 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("test-data/test-data/MaxMind-DB-test-decoder.mmdb") assert.Nil(t, err) var result TestPointerType err = reader.Lookup(net.ParseIP("::1.1.1.0"), &result) require.Nil(t, err) assert.Equal(t, *result.Array, []uint{uint(1), uint(2), uint(3)}) assert.Equal(t, *result.Boolean, true) assert.Equal(t, *result.Bytes, []byte{0x00, 0x00, 0x00, 0x2a}) assert.Equal(t, *result.Double, 42.123456) assert.Equal(t, *result.Float, float32(1.1)) assert.Equal(t, *result.Int32, int32(-268435456)) assert.Equal(t, result.Map.MapX.ArrayX, []int{7, 8, 9}) assert.Equal(t, result.Map.MapX.UTF8StringX, "hello") assert.Equal(t, *result.Uint16, uint16(100)) assert.Equal(t, *result.Uint32, uint32(268435456)) assert.Equal(t, **result.Uint64, uint64(1152921504606846976)) assert.Equal(t, *result.Utf8String, "unicode! ☯ - ♫") bigInt := new(big.Int) bigInt.SetString("1329227995784915872903807060280344576", 10) assert.Equal(t, result.Uint128, bigInt) assert.Nil(t, reader.Close()) } func TestNestedOffsetDecode(t *testing.T) { db, err := Open("test-data/test-data/GeoIP2-City-Test.mmdb") require.Nil(t, err) off, err := db.LookupOffset(net.ParseIP("81.2.69.142")) assert.NotEqual(t, off, NotFound) require.Nil(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.Nil(t, db.Decode(off, &root)) assert.Equal(t, root.Location.Latitude, 51.5142) var longitude float64 assert.Nil(t, db.Decode(root.Location.LongitudeOffset, &longitude)) assert.Equal(t, longitude, -0.0931) var timeZone string assert.Nil(t, db.Decode(root.Location.TimeZoneOffset, &timeZone)) assert.Equal(t, timeZone, "Europe/London") var country struct { IsoCode string `maxminddb:"iso_code"` } assert.Nil(t, db.Decode(root.CountryOffset, &country)) assert.Equal(t, country.IsoCode, "GB") assert.Nil(t, db.Close()) } func TestDecodingUint16IntoInt(t *testing.T) { reader, err := Open("test-data/test-data/MaxMind-DB-test-decoder.mmdb") require.Nil(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.Nil(t, err) assert.Equal(t, result.Uint16, 100) } func TestIpv6inIpv4(t *testing.T) { reader, err := Open("test-data/test-data/MaxMind-DB-test-ipv4-24.mmdb") require.Nil(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, result, emptyResult) expected := errors.New("error looking up '2001::': you attempted to look up an IPv6 address in an IPv4-only database") assert.Equal(t, err, expected) assert.Nil(t, reader.Close(), "error on close") } func TestBrokenDoubleDatabase(t *testing.T) { reader, err := Open("test-data/test-data/GeoIP2-City-Test-Broken-Double-Format.mmdb") require.Nil(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, err, expected) assert.Nil(t, reader.Close(), "error on close") } func TestInvalidNodeCountDatabase(t *testing.T) { _, err := Open("test-data/test-data/GeoIP2-City-Test-Invalid-Node-Count.mmdb") expected := newInvalidDatabaseError("the MaxMind DB contains invalid metadata") assert.Equal(t, err, expected) } 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, err.Error(), "error opening database: invalid MaxMind DB file") } func TestDecodingToNonPointer(t *testing.T) { reader, _ := Open("test-data/test-data/MaxMind-DB-test-decoder.mmdb") var recordInterface interface{} err := reader.Lookup(net.ParseIP("::1.1.1.0"), recordInterface) assert.Equal(t, err.Error(), "result param must be a pointer") assert.Nil(t, reader.Close(), "error on close") } func TestNilLookup(t *testing.T) { reader, _ := Open("test-data/test-data/MaxMind-DB-test-decoder.mmdb") var recordInterface interface{} err := reader.Lookup(nil, recordInterface) assert.Equal(t, err.Error(), "ipAddress passed to Lookup cannot be nil") assert.Nil(t, reader.Close(), "error on close") } func checkMetadata(t *testing.T, reader *Reader, ipVersion uint, recordSize uint) { metadata := reader.Metadata assert.Equal(t, metadata.BinaryFormatMajorVersion, uint(2)) assert.Equal(t, metadata.BinaryFormatMinorVersion, uint(0)) assert.IsType(t, uint(0), metadata.BuildEpoch) assert.Equal(t, metadata.DatabaseType, "Test") assert.Equal(t, metadata.Description, map[string]string{ "en": "Test Database", "zh": "Test Database Chinese", }) assert.Equal(t, metadata.IPVersion, ipVersion) assert.Equal(t, metadata.Languages, []string{"en", "zh"}) if ipVersion == 4 { assert.Equal(t, metadata.NodeCount, uint(164)) } else { assert.Equal(t, metadata.NodeCount, uint(416)) } assert.Equal(t, metadata.RecordSize, 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) } maxminddb-golang-1.2.0/test-data/000077500000000000000000000000001310235557300166145ustar00rootroot00000000000000maxminddb-golang-1.2.0/traverse.go000066400000000000000000000051111310235557300171060ustar00rootroot00000000000000package maxminddb import "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 } // 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 in an IPv6 database. This iterator will iterate over all of these // locations separately. func (r *Reader) Networks() *Networks { s := 4 if r.Metadata.IPVersion == 6 { s = 16 } return &Networks{ reader: r, nodes: []netNode{ { ip: make(net.IP, s), }, }, } } // 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 { for len(n.nodes) > 0 { node := n.nodes[len(n.nodes)-1] n.nodes = n.nodes[:len(n.nodes)-1] for { if node.pointer < n.reader.Metadata.NodeCount { 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)) rightPointer, err := n.reader.readNode(node.pointer, 1) if err != nil { n.err = err return false } node.bit++ n.nodes = append(n.nodes, netNode{ pointer: rightPointer, ip: ipRight, bit: node.bit, }) node.pointer, err = n.reader.readNode(node.pointer, 0) if err != nil { n.err = err return false } } else if node.pointer > n.reader.Metadata.NodeCount { n.lastNode = node return true } else { break } } } 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 err := n.reader.retrieveData(n.lastNode.pointer, result); err != nil { return nil, err } return &net.IPNet{ IP: n.lastNode.ip, Mask: net.CIDRMask(int(n.lastNode.bit), len(n.lastNode.ip)*8), }, nil } // Err returns an error, if any, that was encountered during iteration. func (n *Networks) Err() error { return n.err } maxminddb-golang-1.2.0/traverse_test.go000066400000000000000000000024751310235557300201570ustar00rootroot00000000000000package maxminddb import ( "fmt" "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 := fmt.Sprintf("test-data/test-data/MaxMind-DB-test-ipv%d-%d.mmdb", ipVersion, recordSize) reader, err := Open(fileName) require.Nil(t, err, "unexpected error while opening database: %v", err) defer reader.Close() 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()) } } } func TestNetworksWithInvalidSearchTree(t *testing.T) { reader, err := Open("test-data/test-data/MaxMind-DB-test-broken-search-tree-24.mmdb") require.Nil(t, err, "unexpected error while opening database: %v", err) defer reader.Close() 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, n.Err().Error(), "invalid search tree at 128.128.128.128/32") } maxminddb-golang-1.2.0/verifier.go000066400000000000000000000074671310235557300171060ustar00rootroot00000000000000package maxminddb import "reflect" 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 } return v.verifyDatabase() } 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.2.0/verifier_test.go000066400000000000000000000035551310235557300201370ustar00rootroot00000000000000package maxminddb import ( "github.com/stretchr/testify/assert" "testing" ) func TestVerifyOnGoodDatabases(t *testing.T) { databases := []string{ "test-data/test-data/GeoIP2-Anonymous-IP-Test.mmdb", "test-data/test-data/GeoIP2-City-Test.mmdb", "test-data/test-data/GeoIP2-Connection-Type-Test.mmdb", "test-data/test-data/GeoIP2-Country-Test.mmdb", "test-data/test-data/GeoIP2-Domain-Test.mmdb", "test-data/test-data/GeoIP2-ISP-Test.mmdb", "test-data/test-data/GeoIP2-Precision-City-Test.mmdb", "test-data/test-data/MaxMind-DB-no-ipv4-search-tree.mmdb", "test-data/test-data/MaxMind-DB-string-value-entries.mmdb", "test-data/test-data/MaxMind-DB-test-decoder.mmdb", "test-data/test-data/MaxMind-DB-test-ipv4-24.mmdb", "test-data/test-data/MaxMind-DB-test-ipv4-28.mmdb", "test-data/test-data/MaxMind-DB-test-ipv4-32.mmdb", "test-data/test-data/MaxMind-DB-test-ipv6-24.mmdb", "test-data/test-data/MaxMind-DB-test-ipv6-28.mmdb", "test-data/test-data/MaxMind-DB-test-ipv6-32.mmdb", "test-data/test-data/MaxMind-DB-test-mixed-24.mmdb", "test-data/test-data/MaxMind-DB-test-mixed-28.mmdb", "test-data/test-data/MaxMind-DB-test-mixed-32.mmdb", "test-data/test-data/MaxMind-DB-test-nested.mmdb", } for _, database := range databases { reader, err := Open(database) assert.Nil(t, err) assert.Nil(t, reader.Verify(), "Received error (%v) when verifying %v", err, database) } } func TestVerifyOnBrokenDatabases(t *testing.T) { databases := []string{ "test-data/test-data/GeoIP2-City-Test-Broken-Double-Format.mmdb", "test-data/test-data/MaxMind-DB-test-broken-pointers-24.mmdb", "test-data/test-data/MaxMind-DB-test-broken-search-tree-24.mmdb", } for _, database := range databases { reader, err := Open(database) assert.Nil(t, err) assert.NotNil(t, reader.Verify(), "Did not receive expected error when verifying %v", database, ) } }