buffer_cursor 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. checksums.yaml +7 -0
  2. data/AUTHORS.md +7 -0
  3. data/LICENSE +29 -0
  4. data/README.md +6 -0
  5. data/TODO.md +5 -0
  6. data/lib/buffer_cursor.rb +301 -0
  7. metadata +63 -0
@@ -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
@@ -0,0 +1,7 @@
1
+ # Primary Authors #
2
+
3
+ The primary authors of `buffer_cursor` have been:
4
+
5
+ * Jeremy Cole \<jeremy@jcole.us\>
6
+ * Davi Arnaut \<davi.arnaut@gmail.com\>
7
+
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.
@@ -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,5 @@
1
+ # TODO #
2
+
3
+ [ ] Tracing on a per-instance basis.
4
+ [ ] Redirection of trace output to another file than stdout.
5
+ [ ] Naming of each cursor instance, included in trace output.
@@ -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: []