Class: BufferCursor
- Inherits:
-
Object
- Object
- BufferCursor
- Defined in:
- lib/innodb/util/buffer_cursor.rb
Overview
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.
Defined Under Namespace
Classes: StackEntry
Constant Summary collapse
- VERSION =
"0.9.0"
Class Method Summary collapse
- .global_tracing? ⇒ Boolean
-
.trace!(arg = true) ⇒ Object
Enable tracing for all BufferCursor objects globally.
Instance Method Summary collapse
-
#adjust(relative_position) ⇒ Object
Adjust the current cursor to a new relative position.
-
#backward ⇒ Object
Set the direction of the cursor to “backward”.
-
#current ⇒ Object
The current cursor object; the top of the stack.
-
#direction(direction_arg = nil) ⇒ Object
Return the direction of the current cursor.
-
#each_byte_as_uint8(length, &block) ⇒ Object
Iterate through length bytes returning each as an unsigned 8-bit integer.
-
#forward ⇒ Object
Set the direction of the cursor to “forward”.
-
#initialize(buffer, position) ⇒ BufferCursor
constructor
Initialize a cursor within a buffer at the given position.
- #inspect ⇒ Object
-
#name(name_arg = nil) ⇒ Object
Set the field name.
-
#peek(position = nil) ⇒ Object
Execute a block and restore the cursor to the previous position after the block returns.
-
#pop ⇒ Object
Restore the last cursor position.
- #pop_name ⇒ Object
-
#position ⇒ Object
Return the position of the current cursor.
-
#print_trace(_cursor, position, bytes, name) ⇒ Object
Print a trace output for this cursor.
-
#push(position = nil) ⇒ Object
Save the current cursor position and start a new (nested, stacked) cursor.
- #push_name(name_arg) ⇒ Object
-
#read_and_advance(length) ⇒ Object
Read a number of bytes forwards or backwards from the current cursor position and adjust the cursor position by that amount.
-
#read_bit_array(num_bits) ⇒ Object
Read an array of 1-bit integers.
-
#read_bytes(length) ⇒ Object
Return raw bytes.
-
#read_hex(length) ⇒ Object
Return raw bytes as hex.
-
#read_ic_uint32(flag = nil) ⇒ Object
Read an InnoDB-compressed unsigned 32-bit integer (1-5 bytes).
-
#read_ic_uint64 ⇒ Object
Read an InnoDB-compressed unsigned 64-bit integer (5-9 bytes).
-
#read_imc_uint64 ⇒ Object
Read an InnoDB-“much compressed” unsigned 64-bit integer (1-11 bytes).
-
#read_sint16(position = nil) ⇒ Object
Read a big-endian signed 16-bit integer.
-
#read_string(length) ⇒ Object
Return a null-terminated string.
-
#read_uint16(position = nil) ⇒ Object
Read a big-endian unsigned 16-bit integer.
-
#read_uint24(position = nil) ⇒ Object
Read a big-endian unsigned 24-bit integer.
-
#read_uint32(position = nil) ⇒ Object
Read a big-endian unsigned 32-bit integer.
-
#read_uint48(position = nil) ⇒ Object
Read a big-endian unsigned 48-bit integer.
-
#read_uint64(position = nil) ⇒ Object
Read a big-endian unsigned 64-bit integer.
-
#read_uint8(position = nil) ⇒ Object
Read an unsigned 8-bit integer.
-
#read_uint_array_by_size(size, count) ⇒ Object
Read an array of count unsigned integers given their size in bytes.
-
#read_uint_by_size(size) ⇒ Object
Read a big-endian unsigned integer given its size in bytes.
-
#record_trace(position, bytes, name) ⇒ Object
Generate a trace record from the current cursor.
-
#seek(position) ⇒ Object
Move the current cursor to a new absolute position.
-
#trace(arg = true) ⇒ Object
rubocop:disable Style/OptionalBooleanParameter.
- #trace_to(file) ⇒ Object
-
#trace_with(arg = nil) ⇒ Object
Set a Proc or method on self to trace with.
- #tracing_enabled? ⇒ Boolean
Constructor Details
#initialize(buffer, position) ⇒ BufferCursor
Initialize a cursor within a buffer at the given position.
52 53 54 55 56 57 58 59 |
# 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 |
Class Method Details
.global_tracing? ⇒ Boolean
47 48 49 |
# File 'lib/innodb/util/buffer_cursor.rb', line 47 def self.global_tracing? @global_tracing end |
.trace!(arg = true) ⇒ Object
Enable tracing for all BufferCursor objects globally.
43 44 45 |
# File 'lib/innodb/util/buffer_cursor.rb', line 43 def self.trace!(arg = true) # rubocop:disable Style/OptionalBooleanParameter @global_tracing = arg end |
Instance Method Details
#adjust(relative_position) ⇒ Object
Adjust the current cursor to a new relative position.
176 177 178 179 180 |
# File 'lib/innodb/util/buffer_cursor.rb', line 176 def adjust(relative_position) current.position += relative_position self end |
#backward ⇒ Object
Set the direction of the cursor to “backward”.
159 160 161 |
# File 'lib/innodb/util/buffer_cursor.rb', line 159 def backward direction(:backward) end |
#current ⇒ Object
The current cursor object; the top of the stack.
120 121 122 |
# File 'lib/innodb/util/buffer_cursor.rb', line 120 def current @stack.last end |
#direction(direction_arg = nil) ⇒ Object
Return the direction of the current cursor.
145 146 147 148 149 150 151 |
# 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) ⇒ Object
Iterate through length bytes returning each as an unsigned 8-bit integer.
240 241 242 243 244 245 246 |
# 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 ⇒ Object
Set the direction of the cursor to “forward”.
154 155 156 |
# File 'lib/innodb/util/buffer_cursor.rb', line 154 def forward direction(:forward) end |
#inspect ⇒ Object
61 62 63 64 65 66 67 |
# 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) ⇒ Object
Set the field name.
133 134 135 136 137 138 139 140 141 142 |
# 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) ⇒ Object
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.
202 203 204 205 206 207 208 209 |
# 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 ⇒ Object
Restore the last cursor position.
191 192 193 194 195 196 197 |
# 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 ⇒ Object
128 129 130 |
# File 'lib/innodb/util/buffer_cursor.rb', line 128 def pop_name current.name.pop end |
#position ⇒ Object
Return the position of the current cursor.
164 165 166 |
# File 'lib/innodb/util/buffer_cursor.rb', line 164 def position current.position end |
#print_trace(_cursor, position, bytes, name) ⇒ Object
Print a trace output for this cursor. The method is passed a cursor object, position, raw byte buffer, and array of names.
77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/innodb/util/buffer_cursor.rb', line 77 def print_trace(_cursor, position, bytes, name) slice_size = 16 bytes.each_slice(slice_size).with_index do |slice_bytes, slice_count| @trace_io.puts "%06i %s %-32s %s" % [ position + (slice_count * slice_size), direction == :backward ? "←" : "→", slice_bytes.map { |n| "%02x" % n }.join, slice_count.zero? ? name.join(".") : "↵", ] end end |
#push(position = nil) ⇒ Object
Save the current cursor position and start a new (nested, stacked) cursor.
183 184 185 186 187 188 |
# 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) ⇒ Object
124 125 126 |
# File 'lib/innodb/util/buffer_cursor.rb', line 124 def push_name(name_arg) current.name.push name_arg end |
#read_and_advance(length) ⇒ Object
Read a number of bytes forwards or backwards from the current cursor position and adjust the cursor position by that amount.
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# 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) ⇒ Object
Read an array of 1-bit integers.
402 403 404 405 406 407 |
# 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) ⇒ Object
Return raw bytes.
230 231 232 |
# File 'lib/innodb/util/buffer_cursor.rb', line 230 def read_bytes(length) read_and_advance(length) end |
#read_hex(length) ⇒ Object
Return raw bytes as hex.
249 250 251 |
# 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) ⇒ Object
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).
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 |
# 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 ⇒ Object
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.
364 365 366 367 368 369 370 371 |
# 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 ⇒ Object
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.
381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 |
# 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) ⇒ Object
Read a big-endian signed 16-bit integer.
268 269 270 271 272 |
# 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) ⇒ Object
Return a null-terminated string.
235 236 237 |
# 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) ⇒ Object
Read a big-endian unsigned 16-bit integer.
261 262 263 264 265 |
# 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) ⇒ Object
Read a big-endian unsigned 24-bit integer.
275 276 277 278 279 |
# 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) ⇒ Object
Read a big-endian unsigned 32-bit integer.
282 283 284 285 286 |
# 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) ⇒ Object
Read a big-endian unsigned 48-bit integer.
289 290 291 292 293 |
# 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) ⇒ Object
Read a big-endian unsigned 64-bit integer.
296 297 298 299 300 |
# 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) ⇒ Object
Read an unsigned 8-bit integer.
254 255 256 257 258 |
# 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) ⇒ Object
Read an array of count unsigned integers given their size in bytes.
323 324 325 |
# 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) ⇒ Object
Read a big-endian unsigned integer given its size in bytes.
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 |
# 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) ⇒ Object
Generate a trace record from the current cursor.
115 116 117 |
# 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) ⇒ Object
Move the current cursor to a new absolute position.
169 170 171 172 173 |
# File 'lib/innodb/util/buffer_cursor.rb', line 169 def seek(position) current.position = position if position self end |
#trace(arg = true) ⇒ Object
rubocop:disable Style/OptionalBooleanParameter
69 70 71 72 73 |
# 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) ⇒ Object
89 90 91 92 93 |
# File 'lib/innodb/util/buffer_cursor.rb', line 89 def trace_to(file) @trace_io = file self end |
#trace_with(arg = nil) ⇒ Object
Set a Proc or method on self to trace with.
96 97 98 99 100 101 102 103 104 105 106 107 108 |
# 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? ⇒ Boolean
110 111 112 |
# File 'lib/innodb/util/buffer_cursor.rb', line 110 def tracing_enabled? (self.class.global_tracing? || @instance_tracing) && @trace_proc end |