Class: BufferCursor
- Inherits:
-
Object
- Object
- BufferCursor
- Defined in:
- lib/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_sint16(position = nil) ⇒ Object
Read a big-endian signed 16-bit integer.
-
#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.
-
#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.
-
#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.
-
#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.
40 41 42 43 44 45 46 47 |
# File 'lib/buffer_cursor.rb', line 40 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.
35 36 37 |
# File 'lib/buffer_cursor.rb', line 35 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.
149 150 151 152 |
# File 'lib/buffer_cursor.rb', line 149 def adjust(relative_position) current.position += relative_position self end |
#backward ⇒ Object
Set the direction of the cursor to “backward”.
133 134 135 |
# File 'lib/buffer_cursor.rb', line 133 def backward direction(:backward) end |
#current ⇒ Object
The current cursor object; the top of the stack.
97 98 99 |
# File 'lib/buffer_cursor.rb', line 97 def current @stack.last end |
#direction(direction_arg = nil) ⇒ Object
Return the direction of the current cursor.
118 119 120 121 122 123 124 125 |
# File 'lib/buffer_cursor.rb', line 118 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.
203 204 205 206 207 208 209 210 211 212 213 |
# File 'lib/buffer_cursor.rb', line 203 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”.
128 129 130 |
# File 'lib/buffer_cursor.rb', line 128 def forward direction(:forward) end |
#get_bit_array(num_bits) ⇒ Object
Read an array of 1-bit integers.
295 296 297 298 299 300 |
# File 'lib/buffer_cursor.rb', line 295 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.
198 199 200 |
# File 'lib/buffer_cursor.rb', line 198 def get_bytes(length) read_and_advance(length) end |
#get_hex(length) ⇒ Object
Return raw bytes as hex.
216 217 218 |
# File 'lib/buffer_cursor.rb', line 216 def get_hex(length) read_and_advance(length).bytes.map { |c| "%02x" % c }.join end |
#get_sint16(position = nil) ⇒ Object
Read a big-endian signed 16-bit integer.
235 236 237 238 239 |
# File 'lib/buffer_cursor.rb', line 235 def get_sint16(position=nil) seek(position) data = read_and_advance(2) BinData::Int16be.read(data) end |
#get_uint16(position = nil) ⇒ Object
Read a big-endian unsigned 16-bit integer.
228 229 230 231 232 |
# File 'lib/buffer_cursor.rb', line 228 def get_uint16(position=nil) seek(position) data = read_and_advance(2) BinData::Uint16be.read(data) end |
#get_uint24(position = nil) ⇒ Object
Read a big-endian unsigned 24-bit integer.
242 243 244 245 246 |
# File 'lib/buffer_cursor.rb', line 242 def get_uint24(position=nil) seek(position) data = read_and_advance(3) BinData::Uint24be.read(data) end |
#get_uint32(position = nil) ⇒ Object
Read a big-endian unsigned 32-bit integer.
249 250 251 252 253 |
# File 'lib/buffer_cursor.rb', line 249 def get_uint32(position=nil) seek(position) data = read_and_advance(4) BinData::Uint32be.read(data) end |
#get_uint48(position = nil) ⇒ Object
Read a big-endian unsigned 48-bit integer.
256 257 258 259 260 |
# File 'lib/buffer_cursor.rb', line 256 def get_uint48(position=nil) seek(position) data = read_and_advance(6) BinData::Uint48be.read(data) end |
#get_uint64(position = nil) ⇒ Object
Read a big-endian unsigned 64-bit integer.
263 264 265 266 267 |
# File 'lib/buffer_cursor.rb', line 263 def get_uint64(position=nil) seek(position) data = read_and_advance(8) BinData::Uint64be.read(data) end |
#get_uint8(position = nil) ⇒ Object
Read an unsigned 8-bit integer.
221 222 223 224 225 |
# File 'lib/buffer_cursor.rb', line 221 def get_uint8(position=nil) seek(position) data = read_and_advance(1) BinData::Uint8.read(data) end |
#get_uint_array_by_size(size, count) ⇒ Object
Read an array of count unsigned integers given their size in bytes.
290 291 292 |
# File 'lib/buffer_cursor.rb', line 290 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.
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 |
# File 'lib/buffer_cursor.rb', line 270 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 |
#name(name_arg = nil) ⇒ Object
Set the field name.
102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/buffer_cursor.rb', line 102 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.
171 172 173 174 175 176 177 |
# File 'lib/buffer_cursor.rb', line 171 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.
162 163 164 165 166 |
# File 'lib/buffer_cursor.rb', line 162 def pop raise "No cursors to pop" unless @stack.size > 1 @stack.pop self end |
#position ⇒ Object
Return the position of the current cursor.
138 139 140 |
# File 'lib/buffer_cursor.rb', line 138 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.
56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/buffer_cursor.rb', line 56 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.
155 156 157 158 159 |
# File 'lib/buffer_cursor.rb', line 155 def push(position=nil) @stack.push current.dup seek(position) self 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.
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/buffer_cursor.rb', line 181 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.
92 93 94 |
# File 'lib/buffer_cursor.rb', line 92 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.
143 144 145 146 |
# File 'lib/buffer_cursor.rb', line 143 def seek(position) current.position = position if position self end |
#trace(arg = true) ⇒ Object
49 50 51 52 |
# File 'lib/buffer_cursor.rb', line 49 def trace(arg=true) @instance_tracing = arg self end |
#trace_to(file) ⇒ Object
68 69 70 71 |
# File 'lib/buffer_cursor.rb', line 68 def trace_to(file) @trace_io = file self end |
#trace_with(arg = nil) ⇒ Object
Set a Proc or method on self to trace with.
74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/buffer_cursor.rb', line 74 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
87 88 89 |
# File 'lib/buffer_cursor.rb', line 87 def tracing_enabled? (@@global_tracing or @instance_tracing) && @trace_proc end |