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"
- @@global_tracing =
false
Class Method Summary collapse
-
.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) ⇒ Object
Iterate through length bytes returning each as an unsigned 8-bit integer.
-
#forward ⇒ Object
Set the direction of the cursor to “forward”.
-
#get_bit_array(num_bits) ⇒ Object
Read an array of 1-bit integers.
-
#get_bytes(length) ⇒ Object
Return raw bytes.
-
#get_hex(length) ⇒ Object
Return raw bytes as hex.
-
#get_ic_uint32(flag = nil) ⇒ Object
Read an InnoDB-compressed unsigned 32-bit integer (1-5 bytes).
-
#get_ic_uint64 ⇒ Object
Read an InnoDB-compressed unsigned 64-bit integer (5-9 bytes).
-
#get_imc_uint64 ⇒ Object
Read an InnoDB-“much compressed” unsigned 64-bit integer (1-11 bytes).
-
#get_sint16(position = nil) ⇒ Object
Read a big-endian signed 16-bit integer.
-
#get_string(length) ⇒ Object
Return a null-terminated string.
-
#get_uint16(position = nil) ⇒ Object
Read a big-endian unsigned 16-bit integer.
-
#get_uint24(position = nil) ⇒ Object
Read a big-endian unsigned 24-bit integer.
-
#get_uint32(position = nil) ⇒ Object
Read a big-endian unsigned 32-bit integer.
-
#get_uint48(position = nil) ⇒ Object
Read a big-endian unsigned 48-bit integer.
-
#get_uint64(position = nil) ⇒ Object
Read a big-endian unsigned 64-bit integer.
-
#get_uint8(position = nil) ⇒ Object
Read an unsigned 8-bit integer.
-
#get_uint_array_by_size(size, count) ⇒ Object
Read an array of count unsigned integers given their size in bytes.
-
#get_uint_by_size(size) ⇒ Object
Read a big-endian unsigned integer given its size in bytes.
-
#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.
-
#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
- #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.
48 49 50 51 52 53 54 55 |
# File 'lib/innodb/util/buffer_cursor.rb', line 48 def initialize(buffer, position) @buffer = buffer @stack = [ StackEntry.new(self, position) ] trace false trace_with :print_trace trace_to STDOUT end |
Class Method Details
.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) @@global_tracing = arg end |
Instance Method Details
#adjust(relative_position) ⇒ Object
Adjust the current cursor to a new relative position.
173 174 175 176 |
# File 'lib/innodb/util/buffer_cursor.rb', line 173 def adjust(relative_position) current.position += relative_position self end |
#backward ⇒ Object
Set the direction of the cursor to “backward”.
157 158 159 |
# File 'lib/innodb/util/buffer_cursor.rb', line 157 def backward direction(:backward) end |
#current ⇒ Object
The current cursor object; the top of the stack.
113 114 115 |
# File 'lib/innodb/util/buffer_cursor.rb', line 113 def current @stack.last end |
#direction(direction_arg = nil) ⇒ Object
Return the direction of the current cursor.
142 143 144 145 146 147 148 149 |
# File 'lib/innodb/util/buffer_cursor.rb', line 142 def direction(direction_arg=nil) if direction_arg.nil? return current.direction end current.direction = direction_arg self end |
#each_byte_as_uint8(length) ⇒ Object
Iterate through length bytes returning each as an unsigned 8-bit integer.
232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/innodb/util/buffer_cursor.rb', line 232 def each_byte_as_uint8(length) unless block_given? return enum_for(:each_byte_as_uint8, length) end read_and_advance(length).bytes.each do |byte| yield byte end nil end |
#forward ⇒ Object
Set the direction of the cursor to “forward”.
152 153 154 |
# File 'lib/innodb/util/buffer_cursor.rb', line 152 def forward direction(:forward) end |
#get_bit_array(num_bits) ⇒ Object
Read an array of 1-bit integers.
400 401 402 403 404 405 |
# File 'lib/innodb/util/buffer_cursor.rb', line 400 def get_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 |
#get_bytes(length) ⇒ Object
Return raw bytes.
222 223 224 |
# File 'lib/innodb/util/buffer_cursor.rb', line 222 def get_bytes(length) read_and_advance(length) end |
#get_hex(length) ⇒ Object
Return raw bytes as hex.
245 246 247 |
# File 'lib/innodb/util/buffer_cursor.rb', line 245 def get_hex(length) read_and_advance(length).bytes.map { |c| "%02x" % c }.join end |
#get_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 get_imc_uint64).
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 |
# File 'lib/innodb/util/buffer_cursor.rb', line 331 def get_ic_uint32(flag=nil) name("ic_uint32") { if !flag flag = peek { name("uint8_or_flag") { get_uint8 } } end case when flag < 0x80 adjust(+1) flag when flag < 0xc0 name("uint16") { get_uint16 } & 0x7fff when flag < 0xe0 name("uint24") { get_uint24 } & 0x3fffff when flag < 0xf0 name("uint32") { get_uint32 } & 0x1fffffff when flag == 0xf0 adjust(+1) # Skip the flag byte. name("uint32+1") { get_uint32 } else raise "Invalid flag #{flag.to_s} seen" end } end |
#get_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.
362 363 364 365 366 367 368 369 |
# File 'lib/innodb/util/buffer_cursor.rb', line 362 def get_ic_uint64 name("ic_uint64") { high = name("high") { get_ic_uint32 } low = name("low") { name("uint32") { get_uint32 } } (high << 32) | low } end |
#get_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.
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 |
# File 'lib/innodb/util/buffer_cursor.rb', line 379 def get_imc_uint64 name("imc_uint64") { high = 0 flag = peek { name("uint8_or_flag") { get_uint8 } } if flag == 0xff # The high 32-bits are stored first as an ic_uint32. adjust(+1) # Skip the flag byte. high = name("high") { get_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") { get_ic_uint32(flag) } (high << 32) | low } end |
#get_sint16(position = nil) ⇒ Object
Read a big-endian signed 16-bit integer.
264 265 266 267 268 |
# File 'lib/innodb/util/buffer_cursor.rb', line 264 def get_sint16(position=nil) seek(position) data = read_and_advance(2) BinData::Int16be.read(data).to_i end |
#get_string(length) ⇒ Object
Return a null-terminated string.
227 228 229 |
# File 'lib/innodb/util/buffer_cursor.rb', line 227 def get_string(length) BinData::Stringz.read(read_and_advance(length)) end |
#get_uint16(position = nil) ⇒ Object
Read a big-endian unsigned 16-bit integer.
257 258 259 260 261 |
# File 'lib/innodb/util/buffer_cursor.rb', line 257 def get_uint16(position=nil) seek(position) data = read_and_advance(2) BinData::Uint16be.read(data).to_i end |
#get_uint24(position = nil) ⇒ Object
Read a big-endian unsigned 24-bit integer.
271 272 273 274 275 |
# File 'lib/innodb/util/buffer_cursor.rb', line 271 def get_uint24(position=nil) seek(position) data = read_and_advance(3) BinData::Uint24be.read(data).to_i end |
#get_uint32(position = nil) ⇒ Object
Read a big-endian unsigned 32-bit integer.
278 279 280 281 282 |
# File 'lib/innodb/util/buffer_cursor.rb', line 278 def get_uint32(position=nil) seek(position) data = read_and_advance(4) BinData::Uint32be.read(data).to_i end |
#get_uint48(position = nil) ⇒ Object
Read a big-endian unsigned 48-bit integer.
285 286 287 288 289 |
# File 'lib/innodb/util/buffer_cursor.rb', line 285 def get_uint48(position=nil) seek(position) data = read_and_advance(6) BinData::Uint48be.read(data).to_i end |
#get_uint64(position = nil) ⇒ Object
Read a big-endian unsigned 64-bit integer.
292 293 294 295 296 |
# File 'lib/innodb/util/buffer_cursor.rb', line 292 def get_uint64(position=nil) seek(position) data = read_and_advance(8) BinData::Uint64be.read(data).to_i end |
#get_uint8(position = nil) ⇒ Object
Read an unsigned 8-bit integer.
250 251 252 253 254 |
# File 'lib/innodb/util/buffer_cursor.rb', line 250 def get_uint8(position=nil) seek(position) data = read_and_advance(1) BinData::Uint8.read(data).to_i end |
#get_uint_array_by_size(size, count) ⇒ Object
Read an array of count unsigned integers given their size in bytes.
319 320 321 |
# File 'lib/innodb/util/buffer_cursor.rb', line 319 def get_uint_array_by_size(size, count) (0...count).to_a.inject([]) { |a, n| a << get_uint_by_size(size); a } end |
#get_uint_by_size(size) ⇒ Object
Read a big-endian unsigned integer given its size in bytes.
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 |
# File 'lib/innodb/util/buffer_cursor.rb', line 299 def get_uint_by_size(size) case size when 1 get_uint8 when 2 get_uint16 when 3 get_uint24 when 4 get_uint32 when 6 get_uint48 when 8 get_uint64 else raise "Integer size #{size} not implemented" end end |
#inspect ⇒ Object
57 58 59 60 61 62 63 |
# File 'lib/innodb/util/buffer_cursor.rb', line 57 def inspect "<%s size=%i current=%s>" % [ self.class.name, @buffer.size, current.inspect, ] end |
#name(name_arg = nil) ⇒ Object
Set the field name.
126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/innodb/util/buffer_cursor.rb', line 126 def name(name_arg=nil) if name_arg.nil? return current.name end unless block_given? raise "No block given" end 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.
195 196 197 198 199 200 201 |
# File 'lib/innodb/util/buffer_cursor.rb', line 195 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.
186 187 188 189 190 |
# File 'lib/innodb/util/buffer_cursor.rb', line 186 def pop raise "No cursors to pop" unless @stack.size > 1 @stack.pop self end |
#pop_name ⇒ Object
121 122 123 |
# File 'lib/innodb/util/buffer_cursor.rb', line 121 def pop_name current.name.pop end |
#position ⇒ Object
Return the position of the current cursor.
162 163 164 |
# File 'lib/innodb/util/buffer_cursor.rb', line 162 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.
72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/innodb/util/buffer_cursor.rb', line 72 def print_trace(cursor, position, bytes, name) slice_size = 16 bytes.each_slice(slice_size).each_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 == 0 ? name.join(".") : "↵", ] end end |
#push(position = nil) ⇒ Object
Save the current cursor position and start a new (nested, stacked) cursor.
179 180 181 182 183 |
# File 'lib/innodb/util/buffer_cursor.rb', line 179 def push(position=nil) @stack.push current.dup seek(position) self end |
#push_name(name_arg) ⇒ Object
117 118 119 |
# File 'lib/innodb/util/buffer_cursor.rb', line 117 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.
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'lib/innodb/util/buffer_cursor.rb', line 205 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 |
#record_trace(position, bytes, name) ⇒ Object
Generate a trace record from the current cursor.
108 109 110 |
# File 'lib/innodb/util/buffer_cursor.rb', line 108 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.
167 168 169 170 |
# File 'lib/innodb/util/buffer_cursor.rb', line 167 def seek(position) current.position = position if position self end |
#trace(arg = true) ⇒ Object
65 66 67 68 |
# File 'lib/innodb/util/buffer_cursor.rb', line 65 def trace(arg=true) @instance_tracing = arg self end |
#trace_to(file) ⇒ Object
84 85 86 87 |
# File 'lib/innodb/util/buffer_cursor.rb', line 84 def trace_to(file) @trace_io = file self end |
#trace_with(arg = nil) ⇒ Object
Set a Proc or method on self to trace with.
90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/innodb/util/buffer_cursor.rb', line 90 def trace_with(arg=nil) if arg.nil? @trace_proc = nil elsif arg.class == Proc @trace_proc = arg elsif arg.class == Symbol @trace_proc = lambda { |cursor, position, bytes, name| self.send(arg, cursor, position, bytes, name) } else raise "Don't know how to trace with #{arg}" end self end |
#tracing_enabled? ⇒ Boolean
103 104 105 |
# File 'lib/innodb/util/buffer_cursor.rb', line 103 def tracing_enabled? (@@global_tracing or @instance_tracing) && @trace_proc end |