bindata 2.2.0 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of bindata might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/ChangeLog.rdoc +7 -0
- data/NEWS.rdoc +14 -0
- data/lib/bindata.rb +1 -0
- data/lib/bindata/alignment.rb +4 -3
- data/lib/bindata/array.rb +3 -2
- data/lib/bindata/base.rb +47 -31
- data/lib/bindata/base_primitive.rb +2 -7
- data/lib/bindata/bits.rb +4 -1
- data/lib/bindata/buffer.rb +0 -4
- data/lib/bindata/choice.rb +0 -4
- data/lib/bindata/delayed_io.rb +194 -0
- data/lib/bindata/dsl.rb +17 -23
- data/lib/bindata/framework.rb +5 -0
- data/lib/bindata/int.rb +1 -1
- data/lib/bindata/io.rb +174 -152
- data/lib/bindata/lazy.rb +1 -1
- data/lib/bindata/offset.rb +14 -0
- data/lib/bindata/primitive.rb +1 -0
- data/lib/bindata/sanitize.rb +11 -11
- data/lib/bindata/skip.rb +47 -6
- data/lib/bindata/struct.rb +1 -1
- data/lib/bindata/version.rb +1 -1
- data/lib/bindata/warnings.rb +0 -7
- data/test/alignment_test.rb +4 -0
- data/test/array_test.rb +19 -0
- data/test/base_primitive_test.rb +6 -0
- data/test/base_test.rb +2 -2
- data/test/bits_test.rb +10 -0
- data/test/buffer_test.rb +1 -0
- data/test/delayed_io_test.rb +223 -0
- data/test/int_test.rb +0 -1
- data/test/io_test.rb +64 -0
- data/test/offset_test.rb +1 -0
- data/test/record_test.rb +19 -1
- data/test/skip_test.rb +60 -1
- data/test/string_test.rb +0 -13
- data/test/struct_test.rb +10 -1
- data/test/test_helper.rb +11 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53f36d1668fe4f1636ca00ab18b3764890d80b2b
|
4
|
+
data.tar.gz: bef4e40c419d1a411872e33cda5889d8d7f7d4f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4fecf533fc319ba22a437255557cc72ba48f2b3ac23d7fbc8870b50cbce77ceb8d443e1191646b5683f04f5d8633450b09e71131635399db8c34e7483a2bcadf
|
7
|
+
data.tar.gz: b0336f625a5a88ab2842e55b71b85920982ee66b89e021d7480a4238087f9532bed94c484ba1a44197987cbb67ca0a1ce81fc0a4043fbacaf9c7b405a9b6ce57
|
data/ChangeLog.rdoc
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
= BinData Changelog
|
2
2
|
|
3
|
+
== Version 2.3.0 (2016-03-25)
|
4
|
+
|
5
|
+
* Add :to_abs_offset to Skip.
|
6
|
+
* Added backwards seeking via multi pass I/O. See DelayedIO.
|
7
|
+
* Removed #offset, which was deprecated in 2.1.0.
|
8
|
+
* Removed :adjust_offset. See NEWS.rdoc for details.
|
9
|
+
|
3
10
|
== Version 2.2.0 (2016-01-30)
|
4
11
|
|
5
12
|
* Warn if String has :value but no :read_length. Requested by Michael
|
data/NEWS.rdoc
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
= 2.3.0
|
2
|
+
|
3
|
+
This release adds I/O features.
|
4
|
+
|
5
|
+
Seeking forward to an arbitrary offset is achieved with Skip's :to_abs_offset
|
6
|
+
parameter.
|
7
|
+
|
8
|
+
Multi pass I/O has been added. BinData performs I/O in a single pass. See
|
9
|
+
DelayedIO to perform multi pass (backwards seeking) I/O.
|
10
|
+
|
11
|
+
The experimental :adjust_offset feature has been removed. If you have existing
|
12
|
+
code that uses this feature, you need to explicitly require 'bindata/offset' to
|
13
|
+
retain this functionality.
|
14
|
+
|
1
15
|
= 2.1.0
|
2
16
|
|
3
17
|
Several new features in this release.
|
data/lib/bindata.rb
CHANGED
data/lib/bindata/alignment.rb
CHANGED
data/lib/bindata/array.rb
CHANGED
@@ -217,7 +217,7 @@ module BinData
|
|
217
217
|
index = find_index_of(child)
|
218
218
|
sum = sum_num_bytes_below_index(index)
|
219
219
|
|
220
|
-
child.
|
220
|
+
child.bit_aligned? ? sum.floor : sum.ceil
|
221
221
|
end
|
222
222
|
|
223
223
|
def do_write(io) #:nodoc:
|
@@ -281,8 +281,9 @@ module BinData
|
|
281
281
|
params[:initial_length] = 0
|
282
282
|
end
|
283
283
|
|
284
|
+
params.warn_replacement_parameter(:length, :initial_length)
|
284
285
|
params.warn_replacement_parameter(:read_length, :initial_length)
|
285
|
-
params.must_be_integer(:initial_length
|
286
|
+
params.must_be_integer(:initial_length)
|
286
287
|
|
287
288
|
params.merge!(obj_class.dsl_params)
|
288
289
|
|
data/lib/bindata/base.rb
CHANGED
@@ -2,7 +2,6 @@ require 'bindata/framework'
|
|
2
2
|
require 'bindata/io'
|
3
3
|
require 'bindata/lazy'
|
4
4
|
require 'bindata/name'
|
5
|
-
require 'bindata/offset'
|
6
5
|
require 'bindata/params'
|
7
6
|
require 'bindata/registry'
|
8
7
|
require 'bindata/sanitize'
|
@@ -12,20 +11,21 @@ module BinData
|
|
12
11
|
class Base
|
13
12
|
extend AcceptedParametersPlugin
|
14
13
|
include Framework
|
15
|
-
include CheckOrAdjustOffsetPlugin
|
16
14
|
include RegisterNamePlugin
|
17
15
|
|
18
16
|
class << self
|
19
17
|
# Instantiates this class and reads from +io+, returning the newly
|
20
18
|
# created data object. +args+ will be used when instantiating.
|
21
|
-
def read(io, *args)
|
19
|
+
def read(io, *args, &block)
|
22
20
|
obj = self.new(*args)
|
23
|
-
obj.read(io)
|
21
|
+
obj.read(io, &block)
|
24
22
|
obj
|
25
23
|
end
|
26
24
|
|
27
25
|
# The arg processor for this class.
|
28
26
|
def arg_processor(name = nil)
|
27
|
+
@arg_processor ||= nil
|
28
|
+
|
29
29
|
if name
|
30
30
|
@arg_processor = "#{name}_arg_processor".gsub(/(?:^|_)(.)/) { $1.upcase }.to_sym
|
31
31
|
elsif @arg_processor.is_a? Symbol
|
@@ -49,6 +49,7 @@ module BinData
|
|
49
49
|
|
50
50
|
# Registers all subclasses of this class for use
|
51
51
|
def register_subclasses #:nodoc:
|
52
|
+
singleton_class.send(:undef_method, :inherited)
|
52
53
|
define_singleton_method(:inherited) do |subclass|
|
53
54
|
RegisteredClasses.register(subclass.name, subclass)
|
54
55
|
register_subclasses
|
@@ -138,34 +139,27 @@ module BinData
|
|
138
139
|
end
|
139
140
|
|
140
141
|
# Reads data into this data object.
|
141
|
-
def read(io)
|
142
|
+
def read(io, &block)
|
142
143
|
io = BinData::IO::Read.new(io) unless BinData::IO::Read === io
|
143
144
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
145
|
+
start_read do
|
146
|
+
clear
|
147
|
+
do_read(io)
|
148
|
+
end
|
149
|
+
block.call(self) if block_given?
|
148
150
|
|
149
151
|
self
|
150
152
|
end
|
151
153
|
|
152
|
-
#:nodoc:
|
153
|
-
attr_reader :in_read
|
154
|
-
protected :in_read
|
155
|
-
|
156
|
-
# Returns if this object is currently being read. This is used
|
157
|
-
# internally by BasePrimitive.
|
158
|
-
def reading? #:nodoc:
|
159
|
-
furthest_ancestor.in_read
|
160
|
-
end
|
161
|
-
protected :reading?
|
162
|
-
|
163
154
|
# Writes the value for this data object to +io+.
|
164
|
-
def write(io)
|
155
|
+
def write(io, &block)
|
165
156
|
io = BinData::IO::Write.new(io) unless BinData::IO::Write === io
|
166
157
|
|
167
158
|
do_write(io)
|
168
159
|
io.flush
|
160
|
+
|
161
|
+
block.call(self) if block_given?
|
162
|
+
|
169
163
|
self
|
170
164
|
end
|
171
165
|
|
@@ -175,16 +169,16 @@ module BinData
|
|
175
169
|
end
|
176
170
|
|
177
171
|
# Returns the string representation of this data object.
|
178
|
-
def to_binary_s
|
172
|
+
def to_binary_s(&block)
|
179
173
|
io = BinData::IO.create_string_io
|
180
|
-
write(io)
|
174
|
+
write(io, &block)
|
181
175
|
io.rewind
|
182
176
|
io.read
|
183
177
|
end
|
184
178
|
|
185
179
|
# Returns the hexadecimal string representation of this data object.
|
186
|
-
def to_hex
|
187
|
-
to_binary_s.unpack('H*')[0]
|
180
|
+
def to_hex(&block)
|
181
|
+
to_binary_s(&block).unpack('H*')[0]
|
188
182
|
end
|
189
183
|
|
190
184
|
# Return a human readable representation of this data object.
|
@@ -243,7 +237,7 @@ module BinData
|
|
243
237
|
# A version of +respond_to?+ used by the lazy evaluator. It doesn't
|
244
238
|
# reinvoke the evaluator so as to avoid infinite evaluation loops.
|
245
239
|
def safe_respond_to?(symbol, include_private = false) #:nodoc:
|
246
|
-
|
240
|
+
base_respond_to?(symbol, include_private)
|
247
241
|
end
|
248
242
|
alias_method :base_respond_to?, :respond_to? #:nodoc:
|
249
243
|
|
@@ -254,14 +248,36 @@ module BinData
|
|
254
248
|
self.class.arg_processor.extract_args(self.class, args)
|
255
249
|
end
|
256
250
|
|
257
|
-
def
|
251
|
+
def start_read(&block)
|
252
|
+
top_level_set(:in_read, true)
|
253
|
+
block.call
|
254
|
+
ensure
|
255
|
+
top_level_set(:in_read, false)
|
256
|
+
end
|
257
|
+
|
258
|
+
# Is this object tree currently being read? Used by BasePrimitive.
|
259
|
+
def reading?
|
260
|
+
top_level_get(:in_read)
|
261
|
+
end
|
262
|
+
|
263
|
+
def top_level_set(sym, value)
|
264
|
+
top_level.instance_variable_set("@tl_#{sym}", value)
|
265
|
+
end
|
266
|
+
|
267
|
+
def top_level_get(sym)
|
268
|
+
top_level.instance_variable_defined?("@tl_#{sym}") and
|
269
|
+
top_level.instance_variable_get("@tl_#{sym}")
|
270
|
+
end
|
271
|
+
|
272
|
+
def top_level
|
258
273
|
if parent.nil?
|
259
|
-
self
|
274
|
+
tl = self
|
260
275
|
else
|
261
|
-
|
262
|
-
|
263
|
-
an
|
276
|
+
tl = parent
|
277
|
+
tl = tl.parent while tl.parent
|
264
278
|
end
|
279
|
+
|
280
|
+
tl
|
265
281
|
end
|
266
282
|
|
267
283
|
def binary_string(str)
|
@@ -176,9 +176,7 @@ module BinData
|
|
176
176
|
current_value = snapshot
|
177
177
|
expected = eval_parameter(:assert, :value => current_value)
|
178
178
|
|
179
|
-
msg = if not expected
|
180
|
-
"assertion failed"
|
181
|
-
elsif not expected
|
179
|
+
msg = if not expected
|
182
180
|
"value '#{current_value}' not as expected"
|
183
181
|
elsif expected != true and current_value != expected
|
184
182
|
"value is '#{current_value}' but expected '#{expected}'"
|
@@ -212,10 +210,7 @@ module BinData
|
|
212
210
|
|
213
211
|
def assert_value(current_value)
|
214
212
|
expected = eval_parameter(:asserted_value, :value => current_value)
|
215
|
-
if
|
216
|
-
raise ValidityError,
|
217
|
-
"value '#{current_value}' not as expected for #{debug_name}"
|
218
|
-
elsif current_value != expected and expected != true
|
213
|
+
if current_value != expected
|
219
214
|
raise ValidityError,
|
220
215
|
"value is '#{current_value}' but " +
|
221
216
|
"expected '#{expected}' for #{debug_name}"
|
data/lib/bindata/bits.rb
CHANGED
@@ -40,10 +40,13 @@ module BinData
|
|
40
40
|
#{create_do_num_bytes_code(nbits)}
|
41
41
|
end
|
42
42
|
|
43
|
+
def bit_aligned?
|
44
|
+
true
|
45
|
+
end
|
46
|
+
|
43
47
|
#---------------
|
44
48
|
private
|
45
49
|
|
46
|
-
|
47
50
|
def read_and_return_value(io)
|
48
51
|
#{create_nbits_code(nbits)}
|
49
52
|
val = io.readbits(#{nbits}, :#{endian})
|
data/lib/bindata/buffer.rb
CHANGED
@@ -78,10 +78,6 @@ module BinData
|
|
78
78
|
@type.respond_to?(symbol, include_private) || super
|
79
79
|
end
|
80
80
|
|
81
|
-
def safe_respond_to?(symbol, include_private = false) #:nodoc:
|
82
|
-
base_respond_to?(symbol, include_private)
|
83
|
-
end
|
84
|
-
|
85
81
|
def method_missing(symbol, *args, &block) #:nodoc:
|
86
82
|
@type.__send__(symbol, *args, &block)
|
87
83
|
end
|
data/lib/bindata/choice.rb
CHANGED
@@ -85,10 +85,6 @@ module BinData
|
|
85
85
|
selection
|
86
86
|
end
|
87
87
|
|
88
|
-
def safe_respond_to?(symbol, include_private = false) #:nodoc:
|
89
|
-
base_respond_to?(symbol, include_private)
|
90
|
-
end
|
91
|
-
|
92
88
|
def respond_to?(symbol, include_private = false) #:nodoc:
|
93
89
|
current_choice.respond_to?(symbol, include_private) || super
|
94
90
|
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
require 'bindata/base'
|
2
|
+
require 'bindata/dsl'
|
3
|
+
|
4
|
+
module BinData
|
5
|
+
# BinData declarations are evaluated in a single pass.
|
6
|
+
# However, some binary formats require multi pass processing. A common
|
7
|
+
# reason is seeking backwards in the input stream.
|
8
|
+
#
|
9
|
+
# DelayedIO supports multi pass processing. It works by ignoring the normal
|
10
|
+
# #read or #write calls. The user must explicitly call the #read_now! or
|
11
|
+
# #write_now! methods to process an additional pass. This additional pass
|
12
|
+
# must specify the abs_offset of the I/O operation.
|
13
|
+
#
|
14
|
+
# require 'bindata'
|
15
|
+
#
|
16
|
+
# obj = BinData::DelayedIO.new(:read_abs_offset => 3, :type => :uint16be)
|
17
|
+
# obj.read("\x00\x00\x00\x11\x12")
|
18
|
+
# obj #=> 0
|
19
|
+
#
|
20
|
+
# obj.read_now!
|
21
|
+
# obj #=> 0x1112
|
22
|
+
#
|
23
|
+
# - OR -
|
24
|
+
#
|
25
|
+
# obj.read("\x00\x00\x00\x11\x12") { obj.read_now! } #=> 0x1122
|
26
|
+
#
|
27
|
+
# obj.to_binary_s { obj.write_now! } #=> "\x00\x00\x00\x11\x12"
|
28
|
+
#
|
29
|
+
# You can use the +auto_call_delayed_io+ keyword to cause #read and #write to
|
30
|
+
# automatically perform the extra passes.
|
31
|
+
#
|
32
|
+
# class ReversePascalString < BinData::Record
|
33
|
+
# auto_call_delayed_io
|
34
|
+
#
|
35
|
+
# delayed_io :str, :read_abs_offset => 0 do
|
36
|
+
# string :read_length => :len
|
37
|
+
# end
|
38
|
+
# count_bytes_remaining :total_size
|
39
|
+
# skip :to_abs_offset => lambda { total_size - 1 }
|
40
|
+
# uint8 :len, :value => lambda { str.length }
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# s = ReversePascalString.read("hello\x05")
|
44
|
+
# s.to_binary_s #=> "hello\x05"
|
45
|
+
#
|
46
|
+
#
|
47
|
+
# == Parameters
|
48
|
+
#
|
49
|
+
# Parameters may be provided at initialisation to control the behaviour of
|
50
|
+
# an object. These params are:
|
51
|
+
#
|
52
|
+
# <tt>:read_abs_offset</tt>:: The abs_offset to start reading at.
|
53
|
+
# <tt>:type</tt>:: The single type inside the delayed io. Use
|
54
|
+
# a struct if multiple fields are required.
|
55
|
+
class DelayedIO < BinData::Base
|
56
|
+
extend DSLMixin
|
57
|
+
|
58
|
+
dsl_parser :delayed_io
|
59
|
+
arg_processor :delayed_io
|
60
|
+
|
61
|
+
mandatory_parameters :read_abs_offset, :type
|
62
|
+
|
63
|
+
def initialize_instance
|
64
|
+
@type = get_parameter(:type).instantiate(nil, self)
|
65
|
+
@abs_offset = nil
|
66
|
+
@read_io = nil
|
67
|
+
@write_io = nil
|
68
|
+
end
|
69
|
+
|
70
|
+
def clear?
|
71
|
+
@type.clear?
|
72
|
+
end
|
73
|
+
|
74
|
+
def assign(val)
|
75
|
+
@type.assign(val)
|
76
|
+
end
|
77
|
+
|
78
|
+
def snapshot
|
79
|
+
@type.snapshot
|
80
|
+
end
|
81
|
+
|
82
|
+
def num_bytes
|
83
|
+
@type.num_bytes
|
84
|
+
end
|
85
|
+
|
86
|
+
def respond_to?(symbol, include_private = false) #:nodoc:
|
87
|
+
@type.respond_to?(symbol, include_private) || super
|
88
|
+
end
|
89
|
+
|
90
|
+
def method_missing(symbol, *args, &block) #:nodoc:
|
91
|
+
@type.__send__(symbol, *args, &block)
|
92
|
+
end
|
93
|
+
|
94
|
+
def abs_offset
|
95
|
+
@abs_offset || eval_parameter(:read_abs_offset)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Sets the +abs_offset+ to use when writing this object.
|
99
|
+
def abs_offset=(offset)
|
100
|
+
@abs_offset = offset
|
101
|
+
end
|
102
|
+
|
103
|
+
def rel_offset
|
104
|
+
abs_offset
|
105
|
+
end
|
106
|
+
|
107
|
+
def do_read(io) #:nodoc:
|
108
|
+
@read_io = io
|
109
|
+
end
|
110
|
+
|
111
|
+
def do_write(io) #:nodoc:
|
112
|
+
@write_io = io
|
113
|
+
end
|
114
|
+
|
115
|
+
def do_num_bytes #:nodoc:
|
116
|
+
0
|
117
|
+
end
|
118
|
+
|
119
|
+
# DelayedIO objects aren't read when #read is called.
|
120
|
+
# The reading is delayed until this method is called.
|
121
|
+
def read_now!
|
122
|
+
raise IOError.new "read from where?" unless @read_io
|
123
|
+
|
124
|
+
@read_io.seekbytes(abs_offset - @read_io.offset)
|
125
|
+
start_read do
|
126
|
+
@type.do_read(@read_io)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# DelayedIO objects aren't written when #write is called.
|
131
|
+
# The writing is delayed until this method is called.
|
132
|
+
def write_now!
|
133
|
+
raise IOError.new "write to where?" unless @write_io
|
134
|
+
@write_io.seekbytes(abs_offset - @write_io.offset)
|
135
|
+
@type.do_write(@write_io)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class DelayedIoArgProcessor < BaseArgProcessor
|
140
|
+
include MultiFieldArgSeparator
|
141
|
+
|
142
|
+
def sanitize_parameters!(obj_class, params)
|
143
|
+
params.merge!(obj_class.dsl_params)
|
144
|
+
|
145
|
+
params.must_be_integer(:read_abs_offset)
|
146
|
+
|
147
|
+
if params.needs_sanitizing?(:type)
|
148
|
+
el_type, el_params = params[:type]
|
149
|
+
params[:type] = params.create_sanitized_object_prototype(el_type, el_params)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Add +auto_call_delayed_io+ keyword to BinData::Base.
|
155
|
+
class Base
|
156
|
+
class << self
|
157
|
+
# The +auto_call_delayed_io+ keyword sets a data object tree to perform
|
158
|
+
# multi pass I/O automatically.
|
159
|
+
def auto_call_delayed_io
|
160
|
+
include AutoCallDelayedIO
|
161
|
+
|
162
|
+
DelayedIO.send(:alias_method, :initialize_instance_without_record_io, :initialize_instance)
|
163
|
+
DelayedIO.send(:define_method, :initialize_instance) do
|
164
|
+
if @parent and ! defined? @delayed_io_recorded
|
165
|
+
@delayed_io_recorded = true
|
166
|
+
list = top_level_get(:delayed_ios)
|
167
|
+
list << self if list
|
168
|
+
end
|
169
|
+
|
170
|
+
initialize_instance_without_record_io
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
module AutoCallDelayedIO
|
176
|
+
def initialize_shared_instance
|
177
|
+
top_level_set(:delayed_ios, [])
|
178
|
+
super
|
179
|
+
end
|
180
|
+
|
181
|
+
def read(io)
|
182
|
+
super(io) { top_level_get(:delayed_ios).each { |obj| obj.read_now! } }
|
183
|
+
end
|
184
|
+
|
185
|
+
def write(io, *args)
|
186
|
+
super(io) { top_level_get(:delayed_ios).each { |obj| obj.write_now! } }
|
187
|
+
end
|
188
|
+
|
189
|
+
def num_bytes
|
190
|
+
to_binary_s.size
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|