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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ef9bdd3883fe70283af7a90ef1d7a7aabdbc1043
4
- data.tar.gz: a0e441bd2a6fc0fcebe932b21737364baa53a373
3
+ metadata.gz: 53f36d1668fe4f1636ca00ab18b3764890d80b2b
4
+ data.tar.gz: bef4e40c419d1a411872e33cda5889d8d7f7d4f6
5
5
  SHA512:
6
- metadata.gz: 45b2a84cd80c08590463773919d93cb8aececa94fc391d620e897a4b3b1d2a512dd2e8f91c9ec608008b6007a1828ff1dae80938de1d961eab7403a802d1a696
7
- data.tar.gz: 179ea9774c018fa6d90f2e864d99ae829ee8f41d8289fc30da514481b2dddff94c6b1e6ca9c316f98657a9cfee6c307f001d6d1449db713569c17cb171e06645
6
+ metadata.gz: 4fecf533fc319ba22a437255557cc72ba48f2b3ac23d7fbc8870b50cbce77ceb8d443e1191646b5683f04f5d8633450b09e71131635399db8c34e7483a2bcadf
7
+ data.tar.gz: b0336f625a5a88ab2842e55b71b85920982ee66b89e021d7480a4238087f9532bed94c484ba1a44197987cbb67ca0a1ce81fc0a4043fbacaf9c7b405a9b6ce57
@@ -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.
@@ -15,6 +15,7 @@ require 'bindata/bits'
15
15
  require 'bindata/buffer'
16
16
  require 'bindata/choice'
17
17
  require 'bindata/count_bytes_remaining'
18
+ require 'bindata/delayed_io'
18
19
  require 'bindata/float'
19
20
  require 'bindata/int'
20
21
  require 'bindata/primitive'
@@ -50,9 +50,10 @@ module BinData
50
50
  bytes << @io.readbits(8, :big).chr
51
51
  end
52
52
  end
53
- def method_missing(sym, *args, &block)
54
- @io.send(sym, *args, &block)
55
- end
53
+ end
54
+
55
+ def bit_aligned?
56
+ true
56
57
  end
57
58
 
58
59
  def read_and_return_value(io)
@@ -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.do_num_bytes.is_a?(Integer) ? sum.ceil : sum.floor
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, :read_length)
286
+ params.must_be_integer(:initial_length)
286
287
 
287
288
  params.merge!(obj_class.dsl_params)
288
289
 
@@ -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
- @in_read = true
145
- clear
146
- do_read(io)
147
- @in_read = false
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
- respond_to?(symbol, include_private)
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 furthest_ancestor
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
- an = parent
262
- an = an.parent while an.parent
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 and current_value.nil?
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 not expected
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}"
@@ -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})
@@ -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
@@ -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