class BufferCursor

A cursor to walk through data structures to read fields. The cursor can move forwards, backwards, is seekable, and supports peeking without moving the cursor. The BinData module is used for interpreting bytes as desired.

Constants

VERSION

Public Class Methods

global_tracing?() click to toggle source
# File lib/innodb/util/buffer_cursor.rb, line 47
def self.global_tracing?
  @global_tracing
end
new(buffer, position) click to toggle source

Initialize a cursor within a buffer at the given position.

# File lib/innodb/util/buffer_cursor.rb, line 52
def initialize(buffer, position)
  @buffer = buffer
  @stack = [StackEntry.new(self, position)]

  trace false
  trace_with :print_trace
  trace_to $stdout
end
trace!(arg = true) click to toggle source

Enable tracing for all BufferCursor objects globally.

# File lib/innodb/util/buffer_cursor.rb, line 43
def self.trace!(arg = true) # rubocop:disable Style/OptionalBooleanParameter
  @global_tracing = arg
end

Public Instance Methods

adjust(relative_position) click to toggle source

Adjust the current cursor to a new relative position.

# File lib/innodb/util/buffer_cursor.rb, line 176
def adjust(relative_position)
  current.position += relative_position

  self
end
backward() click to toggle source

Set the direction of the cursor to “backward”.

# File lib/innodb/util/buffer_cursor.rb, line 159
def backward
  direction(:backward)
end
current() click to toggle source

The current cursor object; the top of the stack.

# File lib/innodb/util/buffer_cursor.rb, line 120
def current
  @stack.last
end
direction(direction_arg = nil) click to toggle source

Return the direction of the current cursor.

# File lib/innodb/util/buffer_cursor.rb, line 145
def direction(direction_arg = nil)
  return current.direction if direction_arg.nil?

  current.direction = direction_arg

  self
end
each_byte_as_uint8(length, &block) click to toggle source

Iterate through length bytes returning each as an unsigned 8-bit integer.

# File lib/innodb/util/buffer_cursor.rb, line 240
def each_byte_as_uint8(length, &block)
  return enum_for(:each_byte_as_uint8, length) unless block_given?

  read_and_advance(length).bytes.each(&block)

  nil
end
forward() click to toggle source

Set the direction of the cursor to “forward”.

# File lib/innodb/util/buffer_cursor.rb, line 154
def forward
  direction(:forward)
end
inspect() click to toggle source
# File lib/innodb/util/buffer_cursor.rb, line 61
def inspect
  "<%s size=%i current=%s>" % [
    self.class.name,
    @buffer.size,
    current.inspect,
  ]
end
name(name_arg = nil) { |self| ... } click to toggle source

Set the field name.

# File lib/innodb/util/buffer_cursor.rb, line 133
def name(name_arg = nil)
  return current.name if name_arg.nil?

  raise "No block given" unless block_given?

  current.name.push name_arg
  ret = yield(self)
  current.name.pop
  ret
end
peek(position = nil) { |self| ... } click to toggle source

Execute a block and restore the cursor to the previous position after the block returns. Return the block’s return value after restoring the cursor. Optionally seek to provided position before executing block.

# File lib/innodb/util/buffer_cursor.rb, line 202
def peek(position = nil)
  raise "No block given" unless block_given?

  push(position)
  result = yield(self)
  pop
  result
end
pop() click to toggle source

Restore the last cursor position.

# File lib/innodb/util/buffer_cursor.rb, line 191
def pop
  raise "No cursors to pop" unless @stack.size > 1

  @stack.pop

  self
end
pop_name() click to toggle source
# File lib/innodb/util/buffer_cursor.rb, line 128
def pop_name
  current.name.pop
end
position() click to toggle source

Return the position of the current cursor.

# File lib/innodb/util/buffer_cursor.rb, line 164
def position
  current.position
end
print_trace(_cursor, position, bytes, name) click to toggle source

Print a trace output for this cursor. The method is passed a cursor object, position, raw byte buffer, and array of names.

push(position = nil) click to toggle source

Save the current cursor position and start a new (nested, stacked) cursor.

# File lib/innodb/util/buffer_cursor.rb, line 183
def push(position = nil)
  @stack.push current.dup
  seek(position)

  self
end
push_name(name_arg) click to toggle source
# File lib/innodb/util/buffer_cursor.rb, line 124
def push_name(name_arg)
  current.name.push name_arg
end
read_and_advance(length) click to toggle source

Read a number of bytes forwards or backwards from the current cursor position and adjust the cursor position by that amount.

# File lib/innodb/util/buffer_cursor.rb, line 213
def read_and_advance(length)
  data = nil
  cursor_start = current.position
  case current.direction
  when :forward
    data = @buffer.slice(current.position, length)
    adjust(length)
  when :backward
    adjust(-length)
    data = @buffer.slice(current.position, length)
  end

  record_trace(cursor_start, data.bytes, current.name)
  data
end
read_bit_array(num_bits) click to toggle source

Read an array of 1-bit integers.

# File lib/innodb/util/buffer_cursor.rb, line 402
def read_bit_array(num_bits)
  size = (num_bits + 7) / 8
  data = read_and_advance(size)
  bit_array = BinData::Array.new(type: :bit1, initial_length: size * 8)
  bit_array.read(data).to_ary
end
read_bytes(length) click to toggle source

Return raw bytes.

# File lib/innodb/util/buffer_cursor.rb, line 230
def read_bytes(length)
  read_and_advance(length)
end
read_hex(length) click to toggle source

Return raw bytes as hex.

# File lib/innodb/util/buffer_cursor.rb, line 249
def read_hex(length)
  read_and_advance(length).bytes.map { |c| "%02x" % c }.join
end
read_ic_uint32(flag = nil) click to toggle source

Read an InnoDB-compressed unsigned 32-bit integer (1-5 bytes).

The first byte makes up part of the value stored as well as indicating the number of bytes stored, maximally an additional 4 bytes after the flag for integers >= 0xf0000000.

Optionally accept a flag (first byte) if it has already been read (as is the case in read_imc_uint64).

# File lib/innodb/util/buffer_cursor.rb, line 335
def read_ic_uint32(flag = nil)
  name("ic_uint32") do
    flag ||= peek { name("uint8_or_flag") { read_uint8 } }

    case
    when flag < 0x80
      adjust(+1)
      flag
    when flag < 0xc0
      name("uint16") { read_uint16 } & 0x7fff
    when flag < 0xe0
      name("uint24") { read_uint24 } & 0x3fffff
    when flag < 0xf0
      name("uint32") { read_uint32 } & 0x1fffffff
    when flag == 0xf0
      adjust(+1) # Skip the flag byte.
      name("uint32+1") { read_uint32 }
    else
      raise "Invalid flag #{flag} seen"
    end
  end
end
read_ic_uint64() click to toggle source

Read an InnoDB-compressed unsigned 64-bit integer (5-9 bytes).

The high 32 bits are stored as an InnoDB-compressed unsigned 32-bit integer (1-5 bytes) while the low 32 bits are stored as a standard big-endian 32-bit integer (4 bytes). This makes a combined size of between 5 and 9 bytes.

# File lib/innodb/util/buffer_cursor.rb, line 364
def read_ic_uint64
  name("ic_uint64") do
    high = name("high") { read_ic_uint32 }
    low = name("low") { name("uint32") { read_uint32 } }

    (high << 32) | low
  end
end
read_imc_uint64() click to toggle source

Read an InnoDB-“much compressed” unsigned 64-bit integer (1-11 bytes).

If the first byte is 0xff, this indicates that the high 32 bits are stored immediately afterwards as an InnoDB-compressed 32-bit unsigned integer. If it is any other value it represents the first byte (which is also a flag) of the low 32 bits of the value, also as an InnoDB- compressed 32-bit unsigned integer. This makes for a combined size of between 1 and 11 bytes.

# File lib/innodb/util/buffer_cursor.rb, line 381
def read_imc_uint64
  name("imc_uint64") do
    high = 0
    flag = peek { name("uint8_or_flag") { read_uint8 } }

    if flag == 0xff
      # The high 32-bits are stored first as an ic_uint32.
      adjust(+1) # Skip the flag byte.
      high = name("high") { read_ic_uint32 }
      flag = nil
    end

    # The low 32-bits are stored as an ic_uint32; pass the flag we already
    # read, so we don't have to read it again.
    low = name("low") { read_ic_uint32(flag) }

    (high << 32) | low
  end
end
read_sint16(position = nil) click to toggle source

Read a big-endian signed 16-bit integer.

# File lib/innodb/util/buffer_cursor.rb, line 268
def read_sint16(position = nil)
  seek(position)
  data = read_and_advance(2)
  BinData::Int16be.read(data).to_i
end
read_string(length) click to toggle source

Return a null-terminated string.

# File lib/innodb/util/buffer_cursor.rb, line 235
def read_string(length)
  BinData::Stringz.read(read_and_advance(length))
end
read_uint16(position = nil) click to toggle source

Read a big-endian unsigned 16-bit integer.

# File lib/innodb/util/buffer_cursor.rb, line 261
def read_uint16(position = nil)
  seek(position)
  data = read_and_advance(2)
  BinData::Uint16be.read(data).to_i
end
read_uint24(position = nil) click to toggle source

Read a big-endian unsigned 24-bit integer.

# File lib/innodb/util/buffer_cursor.rb, line 275
def read_uint24(position = nil)
  seek(position)
  data = read_and_advance(3)
  BinData::Uint24be.read(data).to_i
end
read_uint32(position = nil) click to toggle source

Read a big-endian unsigned 32-bit integer.

# File lib/innodb/util/buffer_cursor.rb, line 282
def read_uint32(position = nil)
  seek(position)
  data = read_and_advance(4)
  BinData::Uint32be.read(data).to_i
end
read_uint48(position = nil) click to toggle source

Read a big-endian unsigned 48-bit integer.

# File lib/innodb/util/buffer_cursor.rb, line 289
def read_uint48(position = nil)
  seek(position)
  data = read_and_advance(6)
  BinData::Uint48be.read(data).to_i
end
read_uint64(position = nil) click to toggle source

Read a big-endian unsigned 64-bit integer.

# File lib/innodb/util/buffer_cursor.rb, line 296
def read_uint64(position = nil)
  seek(position)
  data = read_and_advance(8)
  BinData::Uint64be.read(data).to_i
end
read_uint8(position = nil) click to toggle source

Read an unsigned 8-bit integer.

# File lib/innodb/util/buffer_cursor.rb, line 254
def read_uint8(position = nil)
  seek(position)
  data = read_and_advance(1)
  BinData::Uint8.read(data).to_i
end
read_uint_array_by_size(size, count) click to toggle source

Read an array of count unsigned integers given their size in bytes.

# File lib/innodb/util/buffer_cursor.rb, line 323
def read_uint_array_by_size(size, count)
  count.times.map { read_uint_by_size(size) }
end
read_uint_by_size(size) click to toggle source

Read a big-endian unsigned integer given its size in bytes.

# File lib/innodb/util/buffer_cursor.rb, line 303
def read_uint_by_size(size)
  case size
  when 1
    read_uint8
  when 2
    read_uint16
  when 3
    read_uint24
  when 4
    read_uint32
  when 6
    read_uint48
  when 8
    read_uint64
  else
    raise "Integer size #{size} not implemented"
  end
end
record_trace(position, bytes, name) click to toggle source

Generate a trace record from the current cursor.

# File lib/innodb/util/buffer_cursor.rb, line 115
def record_trace(position, bytes, name)
  @trace_proc.call(self, position, bytes, name) if tracing_enabled?
end
seek(position) click to toggle source

Move the current cursor to a new absolute position.

# File lib/innodb/util/buffer_cursor.rb, line 169
def seek(position)
  current.position = position if position

  self
end
trace(arg = true) click to toggle source
# File lib/innodb/util/buffer_cursor.rb, line 69
def trace(arg = true) # rubocop:disable Style/OptionalBooleanParameter
  @instance_tracing = arg

  self
end
trace_to(file) click to toggle source
# File lib/innodb/util/buffer_cursor.rb, line 89
def trace_to(file)
  @trace_io = file

  self
end
trace_with(arg = nil) click to toggle source

Set a Proc or method on self to trace with.

# File lib/innodb/util/buffer_cursor.rb, line 96
def trace_with(arg = nil)
  if arg.nil?
    @trace_proc = nil
  elsif arg.instance_of?(Proc)
    @trace_proc = arg
  elsif arg.instance_of?(Symbol)
    @trace_proc = ->(cursor, position, bytes, name) { send(arg, cursor, position, bytes, name) }
  else
    raise "Don't know how to trace with #{arg}"
  end

  self
end
tracing_enabled?() click to toggle source
# File lib/innodb/util/buffer_cursor.rb, line 110
def tracing_enabled?
  (self.class.global_tracing? || @instance_tracing) && @trace_proc
end