buffer_cursor 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/AUTHORS.md +7 -0
- data/LICENSE +29 -0
- data/README.md +6 -0
- data/TODO.md +5 -0
- data/lib/buffer_cursor.rb +301 -0
- metadata +63 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f0248ae74ccce99afd9a02ea3260385a5091fcf4
|
4
|
+
data.tar.gz: ac5726f7ab26d3fcfdad14ddabc0dbaa5aaa0c04
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 24f0e47f8b8c4f9d8e054caa5bb50e253ddbbcd8f1291a73ab72719bacae64728724f9f9b46f1a129e95daa9c79c60d9d964853e38916b381fb1b31e5418d9fb
|
7
|
+
data.tar.gz: 1e27b54e1776f8ab1f84fbbc08484703e180ed44d54c56be5895d99fb3cc3e90b4c0af8106d677c053097f32bf72e6bcfbdc19c7d1396ebd80f2323abc0c9473
|
data/AUTHORS.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
This software is licensed under the Revised (3-clause) BSD license as follows:
|
2
|
+
|
3
|
+
Copyright (c) 2013, Twitter, Inc.
|
4
|
+
Copyright (c) 2013, Jeremy Cole <jeremy@jcole.us>
|
5
|
+
Copyright (c) 2013, Davi Arnaut <davi.arnaut@gmail.com>
|
6
|
+
|
7
|
+
All rights reserved.
|
8
|
+
|
9
|
+
Redistribution and use in source and binary forms, with or without
|
10
|
+
modification, are permitted provided that the following conditions are met:
|
11
|
+
* Redistributions of source code must retain the above copyright
|
12
|
+
notice, this list of conditions and the following disclaimer.
|
13
|
+
* Redistributions in binary form must reproduce the above copyright
|
14
|
+
notice, this list of conditions and the following disclaimer in the
|
15
|
+
documentation and/or other materials provided with the distribution.
|
16
|
+
* Neither the name of the <organization> nor the
|
17
|
+
names of its contributors may be used to endorse or promote products
|
18
|
+
derived from this software without specific prior written permission.
|
19
|
+
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
21
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
22
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
24
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
25
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
26
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
27
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
28
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
29
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
# A cursor for byte buffers #
|
2
|
+
|
3
|
+
The purpose of this library is to allow easy traversal of buffers of raw data from external sources, such as binary file formats. Often it is necessary to traverse these buffers field-by-field, decoding each field as you go, and in some cases making decisions about the next fields based on the already-encountered ones.
|
4
|
+
|
5
|
+
This library was originally developed to read InnoDB's on-disk file format data structures in [innodb_ruby](https://github.com/jeremycole/innodb_ruby) and due to its usefulness there, was subsequently extracted into its own package for use in other libraries and programs. In the absence of other complete examples it may be useful to look at BufferCursor's usage in `innodb_ruby` as an example.
|
6
|
+
|
data/TODO.md
ADDED
@@ -0,0 +1,301 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require "bindata"
|
4
|
+
|
5
|
+
# A cursor to walk through data structures to read fields. The cursor can move
|
6
|
+
# forwards, backwards, is seekable, and supports peeking without moving the
|
7
|
+
# cursor. The BinData module is used for interpreting bytes as desired.
|
8
|
+
class BufferCursor
|
9
|
+
VERSION = "0.9.0"
|
10
|
+
|
11
|
+
# An entry in a stack of cursors. The cursor position, direction, and
|
12
|
+
# name array are each attributes of the current cursor stack and are
|
13
|
+
# manipulated together.
|
14
|
+
class StackEntry
|
15
|
+
attr_accessor :cursor
|
16
|
+
attr_accessor :position
|
17
|
+
attr_accessor :direction
|
18
|
+
attr_accessor :name
|
19
|
+
|
20
|
+
def initialize(cursor, position=0, direction=:forward, name=nil)
|
21
|
+
@cursor = cursor
|
22
|
+
@position = position
|
23
|
+
@direction = direction
|
24
|
+
@name = name || []
|
25
|
+
end
|
26
|
+
|
27
|
+
def dup
|
28
|
+
StackEntry.new(cursor, position, direction, name.dup)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
@@global_tracing = false
|
33
|
+
|
34
|
+
# Enable tracing for all BufferCursor objects globally.
|
35
|
+
def self.trace!(arg=true)
|
36
|
+
@@global_tracing = arg
|
37
|
+
end
|
38
|
+
|
39
|
+
# Initialize a cursor within a buffer at the given position.
|
40
|
+
def initialize(buffer, position)
|
41
|
+
@buffer = buffer
|
42
|
+
@stack = [ StackEntry.new(self, position) ]
|
43
|
+
|
44
|
+
trace false
|
45
|
+
trace_with :print_trace
|
46
|
+
trace_to STDOUT
|
47
|
+
end
|
48
|
+
|
49
|
+
def trace(arg=true)
|
50
|
+
@instance_tracing = arg
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
# Print a trace output for this cursor. The method is passed a cursor object,
|
55
|
+
# position, raw byte buffer, and array of names.
|
56
|
+
def print_trace(cursor, position, bytes, name)
|
57
|
+
slice_size = 16
|
58
|
+
bytes.each_slice(slice_size).each_with_index do |slice_bytes, slice_count|
|
59
|
+
@trace_io.puts "%06i %s %-32s %s" % [
|
60
|
+
position + (slice_count * slice_size),
|
61
|
+
direction == :backward ? "←" : "→",
|
62
|
+
slice_bytes.map { |n| "%02x" % n }.join,
|
63
|
+
slice_count == 0 ? name.join(".") : "↵",
|
64
|
+
]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def trace_to(file)
|
69
|
+
@trace_io = file
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
# Set a Proc or method on self to trace with.
|
74
|
+
def trace_with(arg=nil)
|
75
|
+
if arg.nil?
|
76
|
+
@trace_proc = nil
|
77
|
+
elsif arg.class == Proc
|
78
|
+
@trace_proc = arg
|
79
|
+
elsif arg.class == Symbol
|
80
|
+
@trace_proc = lambda { |cursor, position, bytes, name| self.send(arg, cursor, position, bytes, name) }
|
81
|
+
else
|
82
|
+
raise "Don't know how to trace with #{arg}"
|
83
|
+
end
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
87
|
+
def tracing_enabled?
|
88
|
+
(@@global_tracing or @instance_tracing) && @trace_proc
|
89
|
+
end
|
90
|
+
|
91
|
+
# Generate a trace record from the current cursor.
|
92
|
+
def record_trace(position, bytes, name)
|
93
|
+
@trace_proc.call(self, position, bytes, name) if tracing_enabled?
|
94
|
+
end
|
95
|
+
|
96
|
+
# The current cursor object; the top of the stack.
|
97
|
+
def current
|
98
|
+
@stack.last
|
99
|
+
end
|
100
|
+
|
101
|
+
# Set the field name.
|
102
|
+
def name(name_arg=nil)
|
103
|
+
if name_arg.nil?
|
104
|
+
return current.name
|
105
|
+
end
|
106
|
+
|
107
|
+
unless block_given?
|
108
|
+
raise "No block given"
|
109
|
+
end
|
110
|
+
|
111
|
+
current.name.push name_arg
|
112
|
+
ret = yield(self)
|
113
|
+
current.name.pop
|
114
|
+
ret
|
115
|
+
end
|
116
|
+
|
117
|
+
# Return the direction of the current cursor.
|
118
|
+
def direction(direction_arg=nil)
|
119
|
+
if direction_arg.nil?
|
120
|
+
return current.direction
|
121
|
+
end
|
122
|
+
|
123
|
+
current.direction = direction_arg
|
124
|
+
self
|
125
|
+
end
|
126
|
+
|
127
|
+
# Set the direction of the cursor to "forward".
|
128
|
+
def forward
|
129
|
+
direction(:forward)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Set the direction of the cursor to "backward".
|
133
|
+
def backward
|
134
|
+
direction(:backward)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Return the position of the current cursor.
|
138
|
+
def position
|
139
|
+
current.position
|
140
|
+
end
|
141
|
+
|
142
|
+
# Move the current cursor to a new absolute position.
|
143
|
+
def seek(position)
|
144
|
+
current.position = position if position
|
145
|
+
self
|
146
|
+
end
|
147
|
+
|
148
|
+
# Adjust the current cursor to a new relative position.
|
149
|
+
def adjust(relative_position)
|
150
|
+
current.position += relative_position
|
151
|
+
self
|
152
|
+
end
|
153
|
+
|
154
|
+
# Save the current cursor position and start a new (nested, stacked) cursor.
|
155
|
+
def push(position=nil)
|
156
|
+
@stack.push current.dup
|
157
|
+
seek(position)
|
158
|
+
self
|
159
|
+
end
|
160
|
+
|
161
|
+
# Restore the last cursor position.
|
162
|
+
def pop
|
163
|
+
raise "No cursors to pop" unless @stack.size > 1
|
164
|
+
@stack.pop
|
165
|
+
self
|
166
|
+
end
|
167
|
+
|
168
|
+
# Execute a block and restore the cursor to the previous position after
|
169
|
+
# the block returns. Return the block's return value after restoring the
|
170
|
+
# cursor. Optionally seek to provided position before executing block.
|
171
|
+
def peek(position=nil)
|
172
|
+
raise "No block given" unless block_given?
|
173
|
+
push(position)
|
174
|
+
result = yield(self)
|
175
|
+
pop
|
176
|
+
result
|
177
|
+
end
|
178
|
+
|
179
|
+
# Read a number of bytes forwards or backwards from the current cursor
|
180
|
+
# position and adjust the cursor position by that amount.
|
181
|
+
def read_and_advance(length)
|
182
|
+
data = nil
|
183
|
+
cursor_start = current.position
|
184
|
+
case current.direction
|
185
|
+
when :forward
|
186
|
+
data = @buffer.slice(current.position, length)
|
187
|
+
adjust(length)
|
188
|
+
when :backward
|
189
|
+
adjust(-length)
|
190
|
+
data = @buffer.slice(current.position, length)
|
191
|
+
end
|
192
|
+
|
193
|
+
record_trace(cursor_start, data.bytes, current.name)
|
194
|
+
data
|
195
|
+
end
|
196
|
+
|
197
|
+
# Return raw bytes.
|
198
|
+
def get_bytes(length)
|
199
|
+
read_and_advance(length)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Iterate through length bytes returning each as an unsigned 8-bit integer.
|
203
|
+
def each_byte_as_uint8(length)
|
204
|
+
unless block_given?
|
205
|
+
return enum_for(:each_byte_as_uint8, length)
|
206
|
+
end
|
207
|
+
|
208
|
+
read_and_advance(length).bytes.each do |byte|
|
209
|
+
yield byte
|
210
|
+
end
|
211
|
+
|
212
|
+
nil
|
213
|
+
end
|
214
|
+
|
215
|
+
# Return raw bytes as hex.
|
216
|
+
def get_hex(length)
|
217
|
+
read_and_advance(length).bytes.map { |c| "%02x" % c }.join
|
218
|
+
end
|
219
|
+
|
220
|
+
# Read an unsigned 8-bit integer.
|
221
|
+
def get_uint8(position=nil)
|
222
|
+
seek(position)
|
223
|
+
data = read_and_advance(1)
|
224
|
+
BinData::Uint8.read(data)
|
225
|
+
end
|
226
|
+
|
227
|
+
# Read a big-endian unsigned 16-bit integer.
|
228
|
+
def get_uint16(position=nil)
|
229
|
+
seek(position)
|
230
|
+
data = read_and_advance(2)
|
231
|
+
BinData::Uint16be.read(data)
|
232
|
+
end
|
233
|
+
|
234
|
+
# Read a big-endian signed 16-bit integer.
|
235
|
+
def get_sint16(position=nil)
|
236
|
+
seek(position)
|
237
|
+
data = read_and_advance(2)
|
238
|
+
BinData::Int16be.read(data)
|
239
|
+
end
|
240
|
+
|
241
|
+
# Read a big-endian unsigned 24-bit integer.
|
242
|
+
def get_uint24(position=nil)
|
243
|
+
seek(position)
|
244
|
+
data = read_and_advance(3)
|
245
|
+
BinData::Uint24be.read(data)
|
246
|
+
end
|
247
|
+
|
248
|
+
# Read a big-endian unsigned 32-bit integer.
|
249
|
+
def get_uint32(position=nil)
|
250
|
+
seek(position)
|
251
|
+
data = read_and_advance(4)
|
252
|
+
BinData::Uint32be.read(data)
|
253
|
+
end
|
254
|
+
|
255
|
+
# Read a big-endian unsigned 48-bit integer.
|
256
|
+
def get_uint48(position=nil)
|
257
|
+
seek(position)
|
258
|
+
data = read_and_advance(6)
|
259
|
+
BinData::Uint48be.read(data)
|
260
|
+
end
|
261
|
+
|
262
|
+
# Read a big-endian unsigned 64-bit integer.
|
263
|
+
def get_uint64(position=nil)
|
264
|
+
seek(position)
|
265
|
+
data = read_and_advance(8)
|
266
|
+
BinData::Uint64be.read(data)
|
267
|
+
end
|
268
|
+
|
269
|
+
# Read a big-endian unsigned integer given its size in bytes.
|
270
|
+
def get_uint_by_size(size)
|
271
|
+
case size
|
272
|
+
when 1
|
273
|
+
get_uint8
|
274
|
+
when 2
|
275
|
+
get_uint16
|
276
|
+
when 3
|
277
|
+
get_uint24
|
278
|
+
when 4
|
279
|
+
get_uint32
|
280
|
+
when 6
|
281
|
+
get_uint48
|
282
|
+
when 8
|
283
|
+
get_uint64
|
284
|
+
else
|
285
|
+
raise "Integer size #{size} not implemented"
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
# Read an array of count unsigned integers given their size in bytes.
|
290
|
+
def get_uint_array_by_size(size, count)
|
291
|
+
(0...count).to_a.inject([]) { |a, n| a << get_uint_by_size(size); a }
|
292
|
+
end
|
293
|
+
|
294
|
+
# Read an array of 1-bit integers.
|
295
|
+
def get_bit_array(num_bits)
|
296
|
+
size = (num_bits + 7) / 8
|
297
|
+
data = read_and_advance(size)
|
298
|
+
bit_array = BinData::Array.new(:type => :bit1, :initial_length => size * 8)
|
299
|
+
bit_array.read(data).to_ary
|
300
|
+
end
|
301
|
+
end
|
metadata
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: buffer_cursor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jeremy Cole
|
8
|
+
- Davi Arnaut
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-02-17 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bindata
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - '>='
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 1.4.5
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - '>='
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 1.4.5
|
28
|
+
description: Cursor for byte buffers
|
29
|
+
email: jeremy@jcole.us
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- LICENSE
|
35
|
+
- AUTHORS.md
|
36
|
+
- README.md
|
37
|
+
- TODO.md
|
38
|
+
- lib/buffer_cursor.rb
|
39
|
+
homepage: https://github.com/jeremycole/buffer_cursor
|
40
|
+
licenses:
|
41
|
+
- New BSD (3-clause)
|
42
|
+
metadata: {}
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - '>='
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
requirements: []
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 2.0.3
|
60
|
+
signing_key:
|
61
|
+
specification_version: 4
|
62
|
+
summary: Cursor for byte buffers
|
63
|
+
test_files: []
|