class MaxMindDB::Client

Constants

DATA_SECTION_SEPARATOR_SIZE
METADATA_BEGIN_MARKER
POINTER_BASE_VALUES
SIZE_BASE_VALUES

Attributes

local_ip_alias[RW]

An IP that is used instead of local IPs

metadata[R]

Public Class Methods

new(path, file_reader = DEFAULT_FILE_READER) click to toggle source
# File lib/maxminddb.rb, line 31
def initialize(path, file_reader = DEFAULT_FILE_READER)
  @path = path
  @data = file_reader.call(path)

  pos = @data.rindex(METADATA_BEGIN_MARKER)
  raise 'invalid file format' unless pos
  pos += METADATA_BEGIN_MARKER.size
  @metadata = decode(pos, 0)[1]

  @ip_version = @metadata['ip_version']
  @node_count = @metadata['node_count']
  @node_byte_size = @metadata['record_size'] * 2 / 8
  @search_tree_size = @node_count * @node_byte_size
end

Public Instance Methods

inspect() click to toggle source
# File lib/maxminddb.rb, line 46
def inspect
  "#<MaxMindDB::Client: DBPath:'#{@path}'>"
end
lookup(ip_or_hostname) click to toggle source
# File lib/maxminddb.rb, line 50
def lookup(ip_or_hostname)
  if @local_ip_alias && is_local?(ip_or_hostname)
    ip_or_hostname = @local_ip_alias
  end

  node_no = 0
  addr = addr_from_ip(ip_or_hostname)
  start_idx = @ip_version == 4 ? 96 : 0
  for i in start_idx ... 128
    flag = (addr >> (127 - i)) & 1
    next_node_no = read_record(node_no, flag)
    if next_node_no == 0
      raise 'invalid file format'
    elsif next_node_no >= @node_count
      data_section_start = @search_tree_size + DATA_SECTION_SEPARATOR_SIZE
      pos = (next_node_no - @node_count) - DATA_SECTION_SEPARATOR_SIZE
      result            = decode(pos, data_section_start)[1]
      result['network'] = network_from_addr(addr, i) unless result.empty?
      return MaxMindDB::Result.new(result)
    else
      node_no = next_node_no
    end
  end
  raise 'invalid file format'
end

Private Instance Methods

addr_from_ip(ip_or_hostname) click to toggle source
# File lib/maxminddb.rb, line 180
def addr_from_ip(ip_or_hostname)
  klass = ip_or_hostname.class

  return ip_or_hostname if RUBY_VERSION.to_f < 2.4 && (klass == Fixnum || klass == Bignum)
  return ip_or_hostname if RUBY_VERSION.to_f >= 2.4 && klass == Integer

  addr = IPAddr.new(ip_or_hostname)
  addr = addr.ipv4_compat if addr.ipv4?
  addr.to_i
end
decode(pos, base_pos) click to toggle source
# File lib/maxminddb.rb, line 92
def decode(pos, base_pos)
  ctrl = @data[pos + base_pos].ord
  pos += 1

  type = ctrl >> 5

  if type == 1 # pointer
    size = ((ctrl >> 3) & 0x3) + 1
    v1 = ctrl & 0x7
    v2 = read_value(pos, base_pos, size)
    pos += size

    pointer = (v1 << (8 * size)) + v2 + POINTER_BASE_VALUES[size]
    val = decode(pointer, base_pos)[1]
  else
    if type == 0 # extended type
      type = 7 + @data[pos + base_pos].ord
      pos += 1
    end

    size = ctrl & 0x1f
    if size >= 29
      byte_size = size - 29 + 1
      val = read_value(pos, base_pos, byte_size)
      pos += byte_size
      size = val + SIZE_BASE_VALUES[byte_size]
    end

    case type
    when 2 # utf8
      val = @data[pos + base_pos, size].encode('utf-8', 'utf-8')
      pos += size
    when 3 # double
      val = @data[pos + base_pos, size].unpack('G')[0]
      pos += size
    when 4 # bytes
      val = @data[pos + base_pos, size]
      pos += size
    when 5 # unsigned 16-bit int
      val = read_value(pos, base_pos, size)
      pos += size
    when 6 # unsigned 32-bit int
      val = read_value(pos, base_pos, size)
      pos += size
    when 7 # map
      val = {}
      size.times do
        pos, k = decode(pos, base_pos)
        pos, v = decode(pos, base_pos)
        val[k] = v
      end
    when 8 # signed 32-bit int
      v1 = @data[pos + base_pos, size].unpack('N')[0]
      bits = size * 8
      val = (v1 & ~(1 << bits)) - (v1 & (1 << bits))
      pos += size
    when 9 # unsigned 64-bit int
      val = read_value(pos, base_pos, size)
      pos += size
    when 10 # unsigned 128-bit int
      val = read_value(pos, base_pos, size)
      pos += size
    when 11 # array
      val = []
      size.times do
        pos, v = decode(pos, base_pos)
        val.push(v)
      end
    when 12 # data cache container
      raise 'TODO:'
    when 13 # end marker
      val = nil
    when 14 # boolean
      val = (size != 0)
    when 15 # float
      val = @data[pos + base_pos, size].unpack('g')[0]
      pos += size
    end
  end

  [pos, val]
end
is_local?(ip_or_hostname) click to toggle source
# File lib/maxminddb.rb, line 201
def is_local?(ip_or_hostname)
  ["127.0.0.1", "localhost", "::1", "0000::1", "0:0:0:0:0:0:0:1"].include? ip_or_hostname
end
network_from_addr(addr, i) click to toggle source
# File lib/maxminddb.rb, line 191
def network_from_addr(addr, i)
  fam  = addr > 4294967295 ? Socket::AF_INET6 : Socket::AF_INET
  ip   = IPAddr.new(addr, family = fam)
  
  subnet_size = ip.ipv4? ? i - 96 + 1 : i + 1
  subnet      = IPAddr.new("#{ip}/#{subnet_size}")
  
  "#{subnet}/#{subnet_size}"
end
read_record(node_no, flag) click to toggle source
# File lib/maxminddb.rb, line 78
def read_record(node_no, flag)
  rec_byte_size = @node_byte_size / 2
  pos = @node_byte_size * node_no
  middle = @data[pos + rec_byte_size].ord if @node_byte_size.odd?
  if flag == 0 # left
    val = read_value(pos, 0, rec_byte_size)
    val += ((middle & 0xf0) << 20) if middle
  else # right
    val = read_value(pos + @node_byte_size - rec_byte_size, 0, rec_byte_size)
    val += ((middle & 0xf) << 24) if middle
  end
  val
end
read_value(pos, base_pos, size) click to toggle source
# File lib/maxminddb.rb, line 175
def read_value(pos, base_pos, size)
  bytes = @data[pos + base_pos, size].unpack('C*')
  bytes.inject(0){|r, v| (r << 8) + v }
end