summaryrefslogtreecommitdiff
path: root/vendor/github.com/oschwald/maxminddb-golang/reader.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/oschwald/maxminddb-golang/reader.go')
-rw-r--r--vendor/github.com/oschwald/maxminddb-golang/reader.go259
1 files changed, 259 insertions, 0 deletions
diff --git a/vendor/github.com/oschwald/maxminddb-golang/reader.go b/vendor/github.com/oschwald/maxminddb-golang/reader.go
new file mode 100644
index 0000000..97b9607
--- /dev/null
+++ b/vendor/github.com/oschwald/maxminddb-golang/reader.go
@@ -0,0 +1,259 @@
+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.
+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 {
+ if r.buffer == nil {
+ return errors.New("cannot call Lookup on a closed database")
+ }
+ 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) {
+ if r.buffer == nil {
+ return 0, errors.New("cannot call LookupOffset on a closed database")
+ }
+ 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 {
+ 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")
+ }
+
+ _, 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
+}