bindata 2.0.0 → 2.1.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/.travis.yml +2 -0
- data/ChangeLog.rdoc +13 -0
- data/NEWS.rdoc +16 -0
- data/README.md +5 -0
- data/bindata.gemspec +1 -0
- data/examples/gzip.rb +1 -1
- data/lib/bindata.rb +1 -1
- data/lib/bindata/array.rb +23 -22
- data/lib/bindata/base.rb +75 -49
- data/lib/bindata/base_primitive.rb +3 -32
- data/lib/bindata/bits.rb +84 -15
- data/lib/bindata/buffer.rb +19 -18
- data/lib/bindata/choice.rb +51 -67
- data/lib/bindata/dsl.rb +268 -161
- data/lib/bindata/framework.rb +0 -11
- data/lib/bindata/int.rb +67 -42
- data/lib/bindata/io.rb +13 -4
- data/lib/bindata/lazy.rb +1 -1
- data/lib/bindata/primitive.rb +9 -8
- data/lib/bindata/record.rb +9 -60
- data/lib/bindata/sanitize.rb +35 -5
- data/lib/bindata/string.rb +33 -34
- data/lib/bindata/struct.rb +166 -109
- data/lib/bindata/version.rb +1 -1
- data/lib/bindata/{deprecated.rb → warnings.rb} +9 -3
- data/test/array_test.rb +3 -3
- data/test/base_primitive_test.rb +0 -34
- data/test/bits_test.rb +38 -34
- data/test/common.rb +4 -0
- data/test/io_test.rb +15 -0
- data/test/lazy_test.rb +11 -3
- data/test/record_test.rb +83 -6
- data/test/struct_test.rb +40 -9
- data/test/system_test.rb +1 -1
- data/test/{deprecated_test.rb → warnings_test.rb} +0 -0
- metadata +18 -4
data/lib/bindata/string.rb
CHANGED
@@ -49,55 +49,27 @@ module BinData
|
|
49
49
|
# from the end of the string. The value will
|
50
50
|
# not be trimmed when writing.
|
51
51
|
class String < BinData::BasePrimitive
|
52
|
+
arg_processor :string
|
52
53
|
|
53
54
|
optional_parameters :read_length, :length, :trim_padding, :pad_front, :pad_left
|
54
55
|
default_parameters :pad_byte => "\0"
|
55
56
|
mutually_exclusive_parameters :read_length, :length
|
56
57
|
mutually_exclusive_parameters :length, :value
|
57
58
|
|
58
|
-
class << self
|
59
|
-
|
60
|
-
def sanitize_parameters!(params) #:nodoc:
|
61
|
-
params.warn_replacement_parameter(:initial_length, :read_length)
|
62
|
-
|
63
|
-
params.warn_renamed_parameter(:pad_char, :pad_byte) # Remove this line in the future
|
64
|
-
|
65
|
-
if params.has_parameter?(:pad_left)
|
66
|
-
params[:pad_front] = params.delete(:pad_left)
|
67
|
-
end
|
68
|
-
|
69
|
-
if params.has_parameter?(:pad_byte)
|
70
|
-
byte = params[:pad_byte]
|
71
|
-
params[:pad_byte] = sanitized_pad_byte(byte)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
#-------------
|
76
|
-
private
|
77
|
-
|
78
|
-
def sanitized_pad_byte(byte)
|
79
|
-
result = byte.is_a?(Integer) ? byte.chr : byte.to_s
|
80
|
-
len = result.respond_to?(:bytesize) ? result.bytesize : result.length
|
81
|
-
if len > 1
|
82
|
-
raise ArgumentError, ":pad_byte must not contain more than 1 byte"
|
83
|
-
end
|
84
|
-
result
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
59
|
def assign(val)
|
89
60
|
super(binary_string(val))
|
90
61
|
end
|
91
62
|
|
92
63
|
def snapshot
|
93
64
|
# override to trim padding
|
94
|
-
|
95
|
-
|
65
|
+
snap = super
|
66
|
+
snap = clamp_to_length(snap)
|
96
67
|
|
97
68
|
if get_parameter(:trim_padding)
|
98
|
-
|
69
|
+
trim_padding(snap)
|
70
|
+
else
|
71
|
+
snap
|
99
72
|
end
|
100
|
-
result
|
101
73
|
end
|
102
74
|
|
103
75
|
#---------------
|
@@ -142,4 +114,31 @@ module BinData
|
|
142
114
|
""
|
143
115
|
end
|
144
116
|
end
|
117
|
+
|
118
|
+
class StringArgProcessor < BaseArgProcessor
|
119
|
+
def sanitize_parameters!(obj_class, params)
|
120
|
+
params.warn_replacement_parameter(:initial_length, :read_length)
|
121
|
+
params.must_be_integer(:read_length, :length)
|
122
|
+
|
123
|
+
if params.has_parameter?(:pad_left)
|
124
|
+
params[:pad_front] = params.delete(:pad_left)
|
125
|
+
end
|
126
|
+
|
127
|
+
if params.has_parameter?(:pad_byte)
|
128
|
+
byte = params[:pad_byte]
|
129
|
+
params[:pad_byte] = sanitized_pad_byte(byte)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
#-------------
|
134
|
+
private
|
135
|
+
|
136
|
+
def sanitized_pad_byte(byte)
|
137
|
+
pad_byte = byte.is_a?(Integer) ? byte.chr : byte.to_s
|
138
|
+
if pad_byte.bytesize > 1
|
139
|
+
raise ArgumentError, ":pad_byte must not contain more than 1 byte"
|
140
|
+
end
|
141
|
+
pad_byte
|
142
|
+
end
|
143
|
+
end
|
145
144
|
end
|
data/lib/bindata/struct.rb
CHANGED
@@ -3,7 +3,7 @@ require 'bindata/base'
|
|
3
3
|
module BinData
|
4
4
|
|
5
5
|
class Base
|
6
|
-
optional_parameter :onlyif # Used by Struct
|
6
|
+
optional_parameter :onlyif, :byte_align # Used by Struct
|
7
7
|
end
|
8
8
|
|
9
9
|
# A Struct is an ordered collection of named data objects.
|
@@ -47,10 +47,13 @@ module BinData
|
|
47
47
|
#
|
48
48
|
# Fields may have have extra parameters as listed below:
|
49
49
|
#
|
50
|
-
# [<tt>:onlyif</tt>]
|
51
|
-
#
|
52
|
-
#
|
50
|
+
# [<tt>:onlyif</tt>] Used to indicate a data object is optional.
|
51
|
+
# if +false+, this object will not be included in any
|
52
|
+
# calls to #read, #write, #num_bytes or #snapshot.
|
53
|
+
# [<tt>:byte_align</tt>] This field's rel_offset must be a multiple of
|
54
|
+
# <tt>:byte_align</tt>.
|
53
55
|
class Struct < BinData::Base
|
56
|
+
arg_processor :struct
|
54
57
|
|
55
58
|
mandatory_parameter :fields
|
56
59
|
optional_parameters :endian, :hide
|
@@ -66,78 +69,11 @@ module BinData
|
|
66
69
|
uniq.collect { |key| [key, true] }.flatten
|
67
70
|
]
|
68
71
|
|
69
|
-
class << self
|
70
|
-
|
71
|
-
def sanitize_parameters!(params) #:nodoc:
|
72
|
-
sanitize_endian(params)
|
73
|
-
sanitize_fields(params)
|
74
|
-
sanitize_hide(params)
|
75
|
-
end
|
76
|
-
|
77
|
-
#-------------
|
78
|
-
private
|
79
|
-
|
80
|
-
def sanitize_endian(params)
|
81
|
-
if params.needs_sanitizing?(:endian)
|
82
|
-
endian = params.create_sanitized_endian(params[:endian])
|
83
|
-
params[:endian] = endian
|
84
|
-
params.endian = endian # sync params[:endian] and params.endian
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def sanitize_fields(params)
|
89
|
-
if params.needs_sanitizing?(:fields)
|
90
|
-
fields = params[:fields]
|
91
|
-
|
92
|
-
params[:fields] = params.create_sanitized_fields
|
93
|
-
fields.each do |ftype, fname, fparams|
|
94
|
-
params[:fields].add_field(ftype, fname, fparams)
|
95
|
-
end
|
96
|
-
|
97
|
-
field_names = sanitized_field_names(params[:fields])
|
98
|
-
ensure_field_names_are_valid(field_names)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
def sanitize_hide(params)
|
103
|
-
if params.needs_sanitizing?(:hide) and params.has_parameter?(:fields)
|
104
|
-
field_names = sanitized_field_names(params[:fields])
|
105
|
-
hfield_names = hidden_field_names(params[:hide])
|
106
|
-
|
107
|
-
params[:hide] = (hfield_names & field_names)
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
def sanitized_field_names(sanitized_fields)
|
112
|
-
sanitized_fields.field_names.compact
|
113
|
-
end
|
114
|
-
|
115
|
-
def hidden_field_names(hidden)
|
116
|
-
(hidden || []).collect { |h| h.to_sym }
|
117
|
-
end
|
118
|
-
|
119
|
-
def ensure_field_names_are_valid(field_names)
|
120
|
-
reserved_names = RESERVED
|
121
|
-
|
122
|
-
field_names.each do |name|
|
123
|
-
if self.class.method_defined?(name)
|
124
|
-
raise NameError.new("Rename field '#{name}' in #{self}, " +
|
125
|
-
"as it shadows an existing method.", name)
|
126
|
-
end
|
127
|
-
if reserved_names.include?(name)
|
128
|
-
raise NameError.new("Rename field '#{name}' in #{self}, " +
|
129
|
-
"as it is a reserved name.", name)
|
130
|
-
end
|
131
|
-
if field_names.count(name) != 1
|
132
|
-
raise NameError.new("field '#{name}' in #{self}, " +
|
133
|
-
"is defined multiple times.", name)
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
72
|
def initialize_shared_instance
|
140
|
-
|
73
|
+
fields = get_parameter(:fields)
|
74
|
+
@field_names = fields.field_names.freeze
|
75
|
+
extend ByteAlignPlugin if fields.any_field_has_parameter?(:byte_align)
|
76
|
+
define_field_accessors
|
141
77
|
super
|
142
78
|
end
|
143
79
|
|
@@ -162,7 +98,7 @@ module BinData
|
|
162
98
|
snapshot = Snapshot.new
|
163
99
|
field_names.each do |name|
|
164
100
|
obj = find_obj_for_name(name)
|
165
|
-
snapshot[name] = obj.snapshot if include_obj(obj)
|
101
|
+
snapshot[name] = obj.snapshot if include_obj?(obj)
|
166
102
|
end
|
167
103
|
snapshot
|
168
104
|
end
|
@@ -179,19 +115,6 @@ module BinData
|
|
179
115
|
end
|
180
116
|
end
|
181
117
|
|
182
|
-
def respond_to?(symbol, include_private = false) #:nodoc:
|
183
|
-
@field_names.include?(base_field_name(symbol)) || super
|
184
|
-
end
|
185
|
-
|
186
|
-
def method_missing(symbol, *args, &block) #:nodoc:
|
187
|
-
obj = find_obj_for_name(symbol)
|
188
|
-
if obj
|
189
|
-
invoke_field(obj, symbol, args)
|
190
|
-
else
|
191
|
-
super
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
118
|
def debug_name_of(child) #:nodoc:
|
196
119
|
field_name = @field_names[find_index_of(child)]
|
197
120
|
"#{debug_name}.#{field_name}"
|
@@ -205,12 +128,12 @@ module BinData
|
|
205
128
|
|
206
129
|
def do_read(io) #:nodoc:
|
207
130
|
instantiate_all_objs
|
208
|
-
@field_objs.each { |f| f.do_read(io) if include_obj(f) }
|
131
|
+
@field_objs.each { |f| f.do_read(io) if include_obj?(f) }
|
209
132
|
end
|
210
133
|
|
211
134
|
def do_write(io) #:nodoc
|
212
135
|
instantiate_all_objs
|
213
|
-
@field_objs.each { |f| f.do_write(io) if include_obj(f) }
|
136
|
+
@field_objs.each { |f| f.do_write(io) if include_obj?(f) }
|
214
137
|
end
|
215
138
|
|
216
139
|
def do_num_bytes #:nodoc:
|
@@ -242,18 +165,25 @@ module BinData
|
|
242
165
|
#---------------
|
243
166
|
private
|
244
167
|
|
245
|
-
def
|
246
|
-
|
168
|
+
def define_field_accessors
|
169
|
+
get_parameter(:fields).each_with_index do |field, i|
|
170
|
+
name = field.name_as_sym
|
171
|
+
define_field_accessors_for(name, i) if name
|
172
|
+
end
|
247
173
|
end
|
248
174
|
|
249
|
-
def
|
250
|
-
name
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
175
|
+
def define_field_accessors_for(name, index)
|
176
|
+
define_singleton_method(name) do
|
177
|
+
instantiate_obj_at(index) if @field_objs[index].nil?
|
178
|
+
@field_objs[index]
|
179
|
+
end
|
180
|
+
define_singleton_method("#{name}=") do |*vals|
|
181
|
+
instantiate_obj_at(index) if @field_objs[index].nil?
|
182
|
+
@field_objs[index].assign(*vals)
|
183
|
+
end
|
184
|
+
define_singleton_method("#{name}?") do
|
185
|
+
instantiate_obj_at(index) if @field_objs[index].nil?
|
186
|
+
include_obj?(@field_objs[index])
|
257
187
|
end
|
258
188
|
end
|
259
189
|
|
@@ -271,6 +201,10 @@ module BinData
|
|
271
201
|
end
|
272
202
|
end
|
273
203
|
|
204
|
+
def base_field_name(name)
|
205
|
+
name.to_s.sub(/(=|\?)\z/, "").to_sym
|
206
|
+
end
|
207
|
+
|
274
208
|
def instantiate_all_objs
|
275
209
|
@field_names.each_index { |i| instantiate_obj_at(i) }
|
276
210
|
end
|
@@ -310,26 +244,25 @@ module BinData
|
|
310
244
|
end
|
311
245
|
|
312
246
|
def sum_num_bytes_below_index(index)
|
313
|
-
sum
|
314
|
-
(0...index).each do |i|
|
247
|
+
(0...index).inject(0) do |sum, i|
|
315
248
|
obj = @field_objs[i]
|
316
|
-
if include_obj(obj)
|
249
|
+
if include_obj?(obj)
|
317
250
|
nbytes = obj.do_num_bytes
|
318
|
-
|
251
|
+
(nbytes.is_a?(Integer) ? sum.ceil : sum) + nbytes
|
252
|
+
else
|
253
|
+
sum
|
319
254
|
end
|
320
255
|
end
|
321
|
-
|
322
|
-
sum
|
323
256
|
end
|
324
257
|
|
325
|
-
def include_obj(obj)
|
258
|
+
def include_obj?(obj)
|
326
259
|
not obj.has_parameter?(:onlyif) or obj.eval_parameter(:onlyif)
|
327
260
|
end
|
328
261
|
|
329
262
|
# A hash that can be accessed via attributes.
|
330
263
|
class Snapshot < ::Hash #:nodoc:
|
331
264
|
def []=(key, value)
|
332
|
-
super
|
265
|
+
super unless value.nil?
|
333
266
|
end
|
334
267
|
|
335
268
|
def respond_to?(symbol, include_private = false)
|
@@ -341,4 +274,128 @@ module BinData
|
|
341
274
|
end
|
342
275
|
end
|
343
276
|
end
|
277
|
+
|
278
|
+
# Align fields to a multiple of :byte_align
|
279
|
+
module ByteAlignPlugin
|
280
|
+
def do_read(io)
|
281
|
+
initial_offset = io.offset
|
282
|
+
instantiate_all_objs
|
283
|
+
@field_objs.each do |f|
|
284
|
+
if include_obj?(f)
|
285
|
+
if align_obj?(f)
|
286
|
+
io.seekbytes(bytes_to_align(f, io.offset - initial_offset))
|
287
|
+
end
|
288
|
+
f.do_read(io)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
def do_write(io)
|
294
|
+
initial_offset = io.offset
|
295
|
+
instantiate_all_objs
|
296
|
+
@field_objs.each do |f|
|
297
|
+
if include_obj?(f)
|
298
|
+
if align_obj?(f)
|
299
|
+
io.writebytes("\x00" * bytes_to_align(f, io.offset - initial_offset))
|
300
|
+
end
|
301
|
+
f.do_write(io)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def sum_num_bytes_below_index(index)
|
307
|
+
sum = 0
|
308
|
+
(0...@field_objs.length).each do |i|
|
309
|
+
obj = @field_objs[i]
|
310
|
+
if include_obj?(obj)
|
311
|
+
sum = sum.ceil + bytes_to_align(obj, sum.ceil) if align_obj?(obj)
|
312
|
+
|
313
|
+
break if i >= index
|
314
|
+
|
315
|
+
nbytes = obj.do_num_bytes
|
316
|
+
sum = (nbytes.is_a?(Integer) ? sum.ceil : sum) + nbytes
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
sum
|
321
|
+
end
|
322
|
+
|
323
|
+
def bytes_to_align(obj, rel_offset)
|
324
|
+
align = obj.eval_parameter(:byte_align)
|
325
|
+
(align - (rel_offset % align)) % align
|
326
|
+
end
|
327
|
+
|
328
|
+
def align_obj?(obj)
|
329
|
+
obj.has_parameter?(:byte_align)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
class StructArgProcessor < BaseArgProcessor
|
334
|
+
def sanitize_parameters!(obj_class, params)
|
335
|
+
sanitize_endian(params)
|
336
|
+
sanitize_fields(obj_class, params)
|
337
|
+
sanitize_hide(params)
|
338
|
+
end
|
339
|
+
|
340
|
+
#-------------
|
341
|
+
private
|
342
|
+
|
343
|
+
def sanitize_endian(params)
|
344
|
+
if params.needs_sanitizing?(:endian)
|
345
|
+
endian = params.create_sanitized_endian(params[:endian])
|
346
|
+
params[:endian] = endian
|
347
|
+
params.endian = endian # sync params[:endian] and params.endian
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
def sanitize_fields(obj_class, params)
|
352
|
+
if params.needs_sanitizing?(:fields)
|
353
|
+
fields = params[:fields]
|
354
|
+
|
355
|
+
params[:fields] = params.create_sanitized_fields
|
356
|
+
fields.each do |ftype, fname, fparams|
|
357
|
+
params[:fields].add_field(ftype, fname, fparams)
|
358
|
+
end
|
359
|
+
|
360
|
+
field_names = sanitized_field_names(params[:fields])
|
361
|
+
ensure_field_names_are_valid(obj_class, field_names)
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
def sanitize_hide(params)
|
366
|
+
if params.needs_sanitizing?(:hide) and params.has_parameter?(:fields)
|
367
|
+
field_names = sanitized_field_names(params[:fields])
|
368
|
+
hfield_names = hidden_field_names(params[:hide])
|
369
|
+
|
370
|
+
params[:hide] = (hfield_names & field_names)
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
def sanitized_field_names(sanitized_fields)
|
375
|
+
sanitized_fields.field_names.compact
|
376
|
+
end
|
377
|
+
|
378
|
+
def hidden_field_names(hidden)
|
379
|
+
(hidden || []).collect { |h| h.to_sym }
|
380
|
+
end
|
381
|
+
|
382
|
+
def ensure_field_names_are_valid(obj_class, field_names)
|
383
|
+
reserved_names = BinData::Struct::RESERVED
|
384
|
+
|
385
|
+
field_names.each do |name|
|
386
|
+
if obj_class.method_defined?(name)
|
387
|
+
raise NameError.new("Rename field '#{name}' in #{obj_class}, " +
|
388
|
+
"as it shadows an existing method.", name)
|
389
|
+
end
|
390
|
+
if reserved_names.include?(name)
|
391
|
+
raise NameError.new("Rename field '#{name}' in #{obj_class}, " +
|
392
|
+
"as it is a reserved name.", name)
|
393
|
+
end
|
394
|
+
if field_names.count(name) != 1
|
395
|
+
raise NameError.new("field '#{name}' in #{obj_class}, " +
|
396
|
+
"is defined multiple times.", name)
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
344
401
|
end
|