class MaxMind::DB::Decoder

Decoder decodes a MaxMind DB data section.

Typically you will interact with this class through a Reader rather than directly.

@!visibility private

Constants

TYPE_DECODER

Public Class Methods

new(io, pointer_base = 0, pointer_test = false) click to toggle source

Create a Decoder.

io is the DB. It must provide a read method. It must be opened in binary mode.

pointer_base is the base number to use when decoding a pointer. It is where the data section begins rather than the beginning of the file. The specification states the formula in the `Data Section Separator' section.

pointer_test is used for testing pointer code.

# File lib/maxmind/db/decoder.rb, line 26
def initialize(io, pointer_base = 0, pointer_test = false)
  @io = io
  @pointer_base = pointer_base
  @pointer_test = pointer_test
end

Public Instance Methods

decode(offset) click to toggle source

Decode a section of the data section starting at offset.

offset is the location of the data structure to decode.

Returns an array where the first element is the decoded value and the second is the offset after decoding it.

Throws an exception if there is an error.

# File lib/maxmind/db/decoder.rb, line 186
def decode(offset)
  new_offset = offset + 1
  buf = @io.read(offset, 1)
  ctrl_byte = buf.ord
  type_num = ctrl_byte >> 5
  type_num, new_offset = read_extended(new_offset) if type_num == 0

  size, new_offset = size_from_ctrl_byte(ctrl_byte, new_offset, type_num)
  # We could check an element exists at `type_num', but for performance I
  # don't.
  send(TYPE_DECODER[type_num], size, new_offset)
end

Private Instance Methods

decode_array(size, offset) click to toggle source
# File lib/maxmind/db/decoder.rb, line 34
def decode_array(size, offset)
  array = []
  size.times do
    value, offset = decode(offset)
    array << value
  end
  [array, offset]
end
decode_boolean(size, offset) click to toggle source
# File lib/maxmind/db/decoder.rb, line 43
def decode_boolean(size, offset)
  [size != 0, offset]
end
decode_bytes(size, offset) click to toggle source
# File lib/maxmind/db/decoder.rb, line 47
def decode_bytes(size, offset)
  [@io.read(offset, size), offset + size]
end
decode_double(size, offset) click to toggle source
# File lib/maxmind/db/decoder.rb, line 51
def decode_double(size, offset)
  verify_size(8, size)
  buf = @io.read(offset, 8)
  [buf.unpack1('G'), offset + 8]
end
decode_float(size, offset) click to toggle source
# File lib/maxmind/db/decoder.rb, line 57
def decode_float(size, offset)
  verify_size(4, size)
  buf = @io.read(offset, 4)
  [buf.unpack1('g'), offset + 4]
end
decode_int(type_code, type_size, size, offset) click to toggle source
# File lib/maxmind/db/decoder.rb, line 86
def decode_int(type_code, type_size, size, offset)
  return 0, offset if size == 0

  buf = @io.read(offset, size)
  buf = buf.rjust(type_size, "\x00") if size != type_size
  [buf.unpack1(type_code), offset + size]
end
decode_int32(size, offset) click to toggle source
# File lib/maxmind/db/decoder.rb, line 70
def decode_int32(size, offset)
  decode_int('l>', 4, size, offset)
end
decode_map(size, offset) click to toggle source
# File lib/maxmind/db/decoder.rb, line 112
def decode_map(size, offset)
  container = {}
  size.times do
    key, offset = decode(offset)
    value, offset = decode(offset)
    container[key] = value
  end
  [container, offset]
end
decode_pointer(size, offset) click to toggle source
# File lib/maxmind/db/decoder.rb, line 122
def decode_pointer(size, offset)
  pointer_size = size >> 3

  case pointer_size
  when 0
    new_offset = offset + 1
    buf = (size & 0x7).chr << @io.read(offset, 1)
    pointer = buf.unpack1('n') + @pointer_base
  when 1
    new_offset = offset + 2
    buf = "\x00".b << (size & 0x7).chr << @io.read(offset, 2)
    pointer = buf.unpack1('N') + 2048 + @pointer_base
  when 2
    new_offset = offset + 3
    buf = (size & 0x7).chr << @io.read(offset, 3)
    pointer = buf.unpack1('N') + 526_336 + @pointer_base
  else
    new_offset = offset + 4
    buf = @io.read(offset, 4)
    pointer = buf.unpack1('N') + @pointer_base
  end

  return pointer, new_offset if @pointer_test

  value, = decode(pointer)
  [value, new_offset]
end
decode_uint128(size, offset) click to toggle source
# File lib/maxmind/db/decoder.rb, line 94
def decode_uint128(size, offset)
  return 0, offset if size == 0

  buf = @io.read(offset, size)

  if size <= 8
    buf = buf.rjust(8, "\x00")
    return buf.unpack1('Q>'), offset + size
  end

  a_bytes = buf[0...-8].rjust(8, "\x00")
  b_bytes = buf[-8...buf.length]
  a = a_bytes.unpack1('Q>')
  b = b_bytes.unpack1('Q>')
  a <<= 64
  [a | b, offset + size]
end
decode_uint16(size, offset) click to toggle source
# File lib/maxmind/db/decoder.rb, line 74
def decode_uint16(size, offset)
  decode_int('n', 2, size, offset)
end
decode_uint32(size, offset) click to toggle source
# File lib/maxmind/db/decoder.rb, line 78
def decode_uint32(size, offset)
  decode_int('N', 4, size, offset)
end
decode_uint64(size, offset) click to toggle source
# File lib/maxmind/db/decoder.rb, line 82
def decode_uint64(size, offset)
  decode_int('Q>', 8, size, offset)
end
decode_utf8_string(size, offset) click to toggle source
# File lib/maxmind/db/decoder.rb, line 150
def decode_utf8_string(size, offset)
  new_offset = offset + size
  buf = @io.read(offset, size)
  buf.force_encoding(Encoding::UTF_8)
  # We could check it's valid UTF-8 with `valid_encoding?', but for
  # performance I do not.
  [buf, new_offset]
end
read_extended(offset) click to toggle source
# File lib/maxmind/db/decoder.rb, line 201
def read_extended(offset)
  buf = @io.read(offset, 1)
  next_byte = buf.ord
  type_num = next_byte + 7
  if type_num < 7
    raise InvalidDatabaseError,
          "Something went horribly wrong in the decoder. An extended type resolved to a type number < 8 (#{type_num})"
  end
  [type_num, offset + 1]
end
size_from_ctrl_byte(ctrl_byte, offset, type_num) click to toggle source
# File lib/maxmind/db/decoder.rb, line 212
def size_from_ctrl_byte(ctrl_byte, offset, type_num)
  size = ctrl_byte & 0x1f

  return size, offset if type_num == 1 || size < 29

  if size == 29
    size_bytes = @io.read(offset, 1)
    size = 29 + size_bytes.ord
    return size, offset + 1
  end

  if size == 30
    size_bytes = @io.read(offset, 2)
    size = 285 + size_bytes.unpack1('n')
    return size, offset + 2
  end

  size_bytes = "\x00".b << @io.read(offset, 3)
  size = 65_821 + size_bytes.unpack1('N')
  [size, offset + 3]
end
verify_size(expected, actual) click to toggle source
# File lib/maxmind/db/decoder.rb, line 63
def verify_size(expected, actual)
  return if expected == actual

  raise InvalidDatabaseError,
        'The MaxMind DB file\'s data section contains bad data (unknown data type or corrupt data)'
end