bindata 0.9.1 → 0.9.2
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.
- data/ChangeLog +8 -0
- data/TODO +0 -6
- data/lib/bindata.rb +1 -1
- data/lib/bindata/array.rb +52 -25
- data/lib/bindata/base.rb +45 -54
- data/lib/bindata/bits.rb +126 -126
- data/lib/bindata/choice.rb +6 -41
- data/lib/bindata/float.rb +4 -4
- data/lib/bindata/int.rb +14 -14
- data/lib/bindata/multi_value.rb +34 -41
- data/lib/bindata/rest.rb +1 -1
- data/lib/bindata/sanitize.rb +88 -3
- data/lib/bindata/single.rb +7 -17
- data/lib/bindata/single_value.rb +4 -10
- data/lib/bindata/string.rb +3 -3
- data/lib/bindata/stringz.rb +1 -1
- data/lib/bindata/struct.rb +118 -139
- data/spec/array_spec.rb +16 -20
- data/spec/base_spec.rb +21 -18
- data/spec/bits_spec.rb +2 -2
- data/spec/choice_spec.rb +0 -24
- data/spec/multi_value_spec.rb +46 -5
- data/spec/sanitize_spec.rb +50 -36
- data/spec/single_spec.rb +0 -8
- data/spec/struct_spec.rb +29 -43
- metadata +2 -2
data/lib/bindata/single.rb
CHANGED
@@ -45,16 +45,11 @@ module BinData
|
|
45
45
|
# parameter. A boolean return indicates success
|
46
46
|
# or failure. Any other return is compared to
|
47
47
|
# the value just read in.
|
48
|
-
class Single < Base
|
48
|
+
class Single < BinData::Base
|
49
49
|
# These are the parameters used by this class.
|
50
50
|
optional_parameters :initial_value, :value, :check_value
|
51
51
|
mutually_exclusive_parameters :initial_value, :value
|
52
52
|
|
53
|
-
# Single objects don't contain fields so this returns an empty list.
|
54
|
-
def self.all_possible_field_names(sanitized_params)
|
55
|
-
[]
|
56
|
-
end
|
57
|
-
|
58
53
|
def initialize(params = {}, env = nil)
|
59
54
|
super(params, env)
|
60
55
|
clear
|
@@ -76,16 +71,6 @@ module BinData
|
|
76
71
|
true
|
77
72
|
end
|
78
73
|
|
79
|
-
# Single objects don't contain fields so this returns an empty list.
|
80
|
-
def field_names
|
81
|
-
[]
|
82
|
-
end
|
83
|
-
|
84
|
-
# Returns a snapshot of this data object.
|
85
|
-
def snapshot
|
86
|
-
value
|
87
|
-
end
|
88
|
-
|
89
74
|
# To be called after calling #do_read.
|
90
75
|
def done_read
|
91
76
|
@in_read = false
|
@@ -122,7 +107,7 @@ module BinData
|
|
122
107
|
current_value = self.value
|
123
108
|
expected = eval_param(:check_value, :value => current_value)
|
124
109
|
if not expected
|
125
|
-
raise ValidityError, "value not as expected"
|
110
|
+
raise ValidityError, "value '#{current_value}' not as expected"
|
126
111
|
elsif current_value != expected and expected != true
|
127
112
|
raise ValidityError, "value is '#{current_value}' but " +
|
128
113
|
"expected '#{expected}'"
|
@@ -141,6 +126,11 @@ module BinData
|
|
141
126
|
val_to_str(_value).length
|
142
127
|
end
|
143
128
|
|
129
|
+
# Returns a snapshot of this data object.
|
130
|
+
def _snapshot
|
131
|
+
value
|
132
|
+
end
|
133
|
+
|
144
134
|
# The unmodified value of this data object. Note that #value calls this
|
145
135
|
# method. This is so that #value can be overridden in subclasses to
|
146
136
|
# modify the value.
|
data/lib/bindata/single_value.rb
CHANGED
@@ -98,7 +98,7 @@ module BinData
|
|
98
98
|
@fields ||= []
|
99
99
|
|
100
100
|
# check that type is known
|
101
|
-
|
101
|
+
unless Sanitizer.type_exists?(type, endian)
|
102
102
|
raise TypeError, "unknown type '#{type}' for #{self}", caller
|
103
103
|
end
|
104
104
|
|
@@ -125,22 +125,16 @@ module BinData
|
|
125
125
|
|
126
126
|
# Returns a sanitized +params+ that is of the form expected
|
127
127
|
# by #initialize.
|
128
|
-
def sanitize_parameters(
|
128
|
+
def sanitize_parameters(sanitizer, params)
|
129
129
|
params = params.dup
|
130
130
|
|
131
|
-
# possibly override endian
|
132
|
-
endian = self.endian || endian
|
133
|
-
|
134
131
|
hash = {}
|
135
132
|
hash[:fields] = self.fields
|
136
|
-
|
137
|
-
unless endian.nil?
|
138
|
-
hash[:endian] = endian
|
139
|
-
end
|
133
|
+
hash[:endian] = self.endian unless self.endian.nil?
|
140
134
|
|
141
135
|
params[:struct_params] = hash
|
142
136
|
|
143
|
-
super(
|
137
|
+
super(sanitizer, params)
|
144
138
|
end
|
145
139
|
end
|
146
140
|
|
data/lib/bindata/string.rb
CHANGED
@@ -45,7 +45,7 @@ module BinData
|
|
45
45
|
# return the value with all pad_chars trimmed
|
46
46
|
# from the end of the string. The value will
|
47
47
|
# not be trimmed when writing.
|
48
|
-
class String < Single
|
48
|
+
class String < BinData::Single
|
49
49
|
|
50
50
|
# Register this class
|
51
51
|
register(self.name, self)
|
@@ -60,7 +60,7 @@ module BinData
|
|
60
60
|
|
61
61
|
# Returns a sanitized +params+ that is of the form expected
|
62
62
|
# by #initialize.
|
63
|
-
def sanitize_parameters(params, *args)
|
63
|
+
def sanitize_parameters(sanitizer, params, *args)
|
64
64
|
params = params.dup
|
65
65
|
|
66
66
|
# warn about deprecated param - remove before releasing 1.0
|
@@ -79,7 +79,7 @@ module BinData
|
|
79
79
|
params[:pad_char] = ch
|
80
80
|
end
|
81
81
|
|
82
|
-
super(params, *args)
|
82
|
+
super(sanitizer, params, *args)
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
data/lib/bindata/stringz.rb
CHANGED
data/lib/bindata/struct.rb
CHANGED
@@ -15,8 +15,8 @@ module BinData
|
|
15
15
|
# obj = BinData::Struct.new(:hide => :a,
|
16
16
|
# :fields => [ [:int32le, :a],
|
17
17
|
# [:int16le, :b],
|
18
|
-
# [:tuple, :
|
19
|
-
# obj.field_names =># ["b", "
|
18
|
+
# [:tuple, :s] ])
|
19
|
+
# obj.field_names =># ["b", "s"]
|
20
20
|
#
|
21
21
|
#
|
22
22
|
# == Parameters
|
@@ -27,10 +27,9 @@ module BinData
|
|
27
27
|
# <tt>:fields</tt>:: An array specifying the fields for this struct.
|
28
28
|
# Each element of the array is of the form [type, name,
|
29
29
|
# params]. Type is a symbol representing a registered
|
30
|
-
# type. Name is the name of this field.
|
31
|
-
#
|
32
|
-
#
|
33
|
-
# instantiating it.
|
30
|
+
# type. Name is the name of this field. Params is an
|
31
|
+
# optional hash of parameters to pass to this field
|
32
|
+
# when instantiating it.
|
34
33
|
# <tt>:hide</tt>:: A list of the names of fields that are to be hidden
|
35
34
|
# from the outside world. Hidden fields don't appear
|
36
35
|
# in #snapshot or #field_names but are still accessible
|
@@ -40,6 +39,13 @@ module BinData
|
|
40
39
|
# nested data objects.
|
41
40
|
class Struct < BinData::Base
|
42
41
|
|
42
|
+
# These reserved words may not be used as field names
|
43
|
+
RESERVED = (::Hash.instance_methods +
|
44
|
+
%w{alias and begin break case class def defined do else elsif
|
45
|
+
end ensure false for if in module next nil not or redo
|
46
|
+
rescue retry return self super then true undef unless until
|
47
|
+
when while yield }).uniq
|
48
|
+
|
43
49
|
# Register this class
|
44
50
|
register(self.name, self)
|
45
51
|
|
@@ -74,7 +80,6 @@ module BinData
|
|
74
80
|
# note that fields are stored in an instance variable not a class var
|
75
81
|
@hide ||= []
|
76
82
|
args.each do |name|
|
77
|
-
next if name.nil?
|
78
83
|
@hide << name.to_s
|
79
84
|
end
|
80
85
|
@hide
|
@@ -86,56 +91,51 @@ module BinData
|
|
86
91
|
name, params = args
|
87
92
|
|
88
93
|
type = symbol
|
89
|
-
name =
|
94
|
+
name = name.to_s
|
90
95
|
params ||= {}
|
91
96
|
|
92
97
|
# note that fields are stored in an instance variable not a class var
|
93
98
|
@fields ||= []
|
94
99
|
|
95
100
|
# check that type is known
|
96
|
-
|
101
|
+
unless Sanitizer.type_exists?(type, endian)
|
97
102
|
raise TypeError, "unknown type '#{type}' for #{self}", caller
|
98
103
|
end
|
99
104
|
|
100
|
-
# check
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
if n == name
|
105
|
-
raise SyntaxError, "duplicate field '#{name}' in #{self}", caller
|
106
|
-
end
|
105
|
+
# check for duplicate names
|
106
|
+
@fields.each do |t, n, p|
|
107
|
+
if n == name
|
108
|
+
raise SyntaxError, "duplicate field '#{name}' in #{self}", caller
|
107
109
|
end
|
110
|
+
end
|
108
111
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
112
|
+
# check that name doesn't shadow an existing method
|
113
|
+
if self.instance_methods.include?(name)
|
114
|
+
raise NameError.new("", name),
|
115
|
+
"field '#{name}' shadows an existing method", caller
|
116
|
+
end
|
114
117
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
end
|
118
|
+
# check that name isn't reserved
|
119
|
+
if self::RESERVED.include?(name)
|
120
|
+
raise NameError.new("", name),
|
121
|
+
"field '#{name}' is a reserved name", caller
|
120
122
|
end
|
121
123
|
|
122
124
|
# remember this field. These fields will be recalled upon creating
|
123
125
|
# an instance of this class
|
124
126
|
@fields.push([type, name, params])
|
125
127
|
end
|
126
|
-
def deprecated_hack(params
|
128
|
+
def deprecated_hack(params)
|
127
129
|
params = params.dup
|
128
130
|
|
129
131
|
# possibly override endian
|
130
|
-
endian = params[:endian] || self.endian
|
131
|
-
unless endian.nil?
|
132
|
-
params[:endian] = endian
|
133
|
-
end
|
132
|
+
endian = params[:endian] || self.endian
|
133
|
+
params[:endian] = endian unless endian.nil?
|
134
134
|
|
135
135
|
params[:fields] = params[:fields] || self.fields
|
136
136
|
params[:hide] = params[:hide] || self.hide
|
137
137
|
|
138
|
-
|
138
|
+
params
|
139
139
|
end
|
140
140
|
#
|
141
141
|
#### DEPRECATION HACK to allow inheriting from BinData::Struct
|
@@ -143,17 +143,17 @@ module BinData
|
|
143
143
|
|
144
144
|
# Returns a sanitized +params+ that is of the form expected
|
145
145
|
# by #initialize.
|
146
|
-
def sanitize_parameters(
|
146
|
+
def sanitize_parameters(sanitizer, params)
|
147
147
|
#### DEPRECATION HACK to allow inheriting from BinData::Struct
|
148
148
|
#
|
149
|
-
params
|
149
|
+
params = deprecated_hack(params)
|
150
150
|
#
|
151
151
|
#### DEPRECATION HACK to allow inheriting from BinData::Struct
|
152
152
|
|
153
153
|
params = params.dup
|
154
154
|
|
155
155
|
# possibly override endian
|
156
|
-
endian = params[:endian]
|
156
|
+
endian = params[:endian]
|
157
157
|
if endian != nil
|
158
158
|
unless [:little, :big].include?(endian)
|
159
159
|
raise ArgumentError, "unknown value for endian '#{endian}'"
|
@@ -163,87 +163,59 @@ module BinData
|
|
163
163
|
end
|
164
164
|
|
165
165
|
if params.has_key?(:fields)
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
166
|
+
sanitizer.with_endian(endian) do
|
167
|
+
# ensure names of fields are strings and that params is sanitized
|
168
|
+
all_fields = params[:fields].collect do |ftype, fname, fparams|
|
169
|
+
fname = fname.to_s
|
170
|
+
klass, new_params = sanitizer.sanitize(ftype, fparams)
|
171
|
+
[klass, fname, new_params]
|
172
|
+
end
|
173
|
+
params[:fields] = all_fields
|
172
174
|
end
|
173
|
-
params[:fields] = all_fields
|
174
175
|
|
175
176
|
# collect all hidden names that correspond to a field name
|
176
177
|
hide = []
|
177
178
|
if params.has_key?(:hide)
|
178
|
-
hidden = params[:hide] || []
|
179
|
-
|
180
|
-
|
181
|
-
h = h.to_s
|
182
|
-
hide << h if all_fields.find { |k,n,p| n == h }
|
183
|
-
end
|
179
|
+
hidden = (params[:hide] || []).collect { |h| h.to_s }
|
180
|
+
all_field_names = params[:fields].collect { |k,n,p| n }
|
181
|
+
hide = hidden & all_field_names
|
184
182
|
end
|
185
183
|
params[:hide] = hide
|
186
184
|
end
|
187
185
|
|
188
186
|
# obtain SanitizedParameters
|
189
|
-
params = super(
|
187
|
+
params = super(sanitizer, params)
|
190
188
|
|
191
189
|
# now params are sanitized, check that parameter names are okay
|
192
190
|
|
193
191
|
field_names = []
|
194
192
|
instance_methods = self.instance_methods
|
195
|
-
reserved_names =
|
193
|
+
reserved_names = RESERVED
|
196
194
|
|
197
195
|
params[:fields].each do |fklass, fname, fparams|
|
198
196
|
|
199
197
|
# check that name doesn't shadow an existing method
|
200
198
|
if instance_methods.include?(fname)
|
201
|
-
raise NameError.new("field '#{fname}'
|
199
|
+
raise NameError.new("Rename field '#{fname}' in #{self}, " +
|
200
|
+
"as it shadows an existing method.", fname)
|
202
201
|
end
|
203
202
|
|
204
203
|
# check that name isn't reserved
|
205
204
|
if reserved_names.include?(fname)
|
206
|
-
raise NameError.new("field '#{fname}'
|
205
|
+
raise NameError.new("Rename field '#{fname}' in #{self}, " +
|
206
|
+
"as it is a reserved name.", fname)
|
207
207
|
end
|
208
208
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
end
|
214
|
-
field_names << name
|
215
|
-
end
|
216
|
-
else
|
217
|
-
if field_names.include?(fname)
|
218
|
-
raise NameError.new("field '#{fname}' is defined multiple times in #{self}.", fname)
|
219
|
-
end
|
220
|
-
field_names << fname
|
209
|
+
# check for multiple definitions
|
210
|
+
if field_names.include?(fname)
|
211
|
+
raise NameError.new("field '#{fname}' in #{self}, " +
|
212
|
+
"is defined multiple times.", fname)
|
221
213
|
end
|
222
|
-
end
|
223
214
|
|
224
|
-
|
225
|
-
end
|
226
|
-
|
227
|
-
# Returns a list of the names of all possible field names for a Struct
|
228
|
-
# created with +sanitized_params+. Hidden names will not be included
|
229
|
-
# in the returned list.
|
230
|
-
def all_possible_field_names(sanitized_params)
|
231
|
-
unless SanitizedParameters === sanitized_params
|
232
|
-
raise ArgumentError, "parameters aren't sanitized"
|
215
|
+
field_names << fname
|
233
216
|
end
|
234
217
|
|
235
|
-
|
236
|
-
|
237
|
-
names = []
|
238
|
-
sanitized_params[:fields].each do |fklass, fname, fparams|
|
239
|
-
if fname == ""
|
240
|
-
names.concat(fklass.all_possible_field_names(fparams))
|
241
|
-
else
|
242
|
-
names << fname unless hidden_names.include?(fname)
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
names
|
218
|
+
params
|
247
219
|
end
|
248
220
|
end
|
249
221
|
|
@@ -255,19 +227,19 @@ module BinData
|
|
255
227
|
def initialize(params = {}, env = nil)
|
256
228
|
super(params, env)
|
257
229
|
|
258
|
-
#
|
259
|
-
@
|
260
|
-
|
261
|
-
end
|
230
|
+
# extract field names but don't instantiate the fields
|
231
|
+
@field_names = param(:fields).collect { |k, n, p| n }
|
232
|
+
@field_objs = []
|
262
233
|
end
|
263
234
|
|
264
235
|
# Clears the field represented by +name+. If no +name+
|
265
236
|
# is given, clears all fields in the struct.
|
266
237
|
def clear(name = nil)
|
267
238
|
if name.nil?
|
268
|
-
|
239
|
+
@field_objs.each { |f| f.clear unless f.nil? }
|
269
240
|
else
|
270
|
-
find_obj_for_name(name.to_s)
|
241
|
+
obj = find_obj_for_name(name.to_s)
|
242
|
+
obj.clear unless obj.nil?
|
271
243
|
end
|
272
244
|
end
|
273
245
|
|
@@ -275,10 +247,13 @@ module BinData
|
|
275
247
|
# is given, returns whether all fields are clear.
|
276
248
|
def clear?(name = nil)
|
277
249
|
if name.nil?
|
278
|
-
|
250
|
+
@field_objs.each do |f|
|
251
|
+
return false unless f.nil? or f.clear?
|
252
|
+
end
|
279
253
|
true
|
280
254
|
else
|
281
|
-
find_obj_for_name(name.to_s)
|
255
|
+
obj = find_obj_for_name(name.to_s)
|
256
|
+
obj.nil? ? true : obj.clear?
|
282
257
|
end
|
283
258
|
end
|
284
259
|
|
@@ -295,65 +270,47 @@ module BinData
|
|
295
270
|
# collect field names
|
296
271
|
names = []
|
297
272
|
hidden = param(:hide)
|
298
|
-
@
|
299
|
-
if
|
300
|
-
|
301
|
-
names << name
|
302
|
-
end
|
303
|
-
else
|
304
|
-
names.concat(obj.field_names)
|
273
|
+
@field_names.each do |name|
|
274
|
+
if include_hidden or not hidden.include?(name)
|
275
|
+
names << name
|
305
276
|
end
|
306
277
|
end
|
307
278
|
names
|
308
279
|
end
|
309
280
|
|
310
|
-
# Returns a snapshot of this struct as a hash.
|
311
|
-
def snapshot
|
312
|
-
hash = Snapshot.new
|
313
|
-
field_names.each do |name|
|
314
|
-
hash[name] = find_obj_for_name(name).snapshot
|
315
|
-
end
|
316
|
-
hash
|
317
|
-
end
|
318
|
-
|
319
281
|
# To be called after calling #read.
|
320
282
|
def done_read
|
321
|
-
|
283
|
+
@field_objs.each { |f| f.done_read unless f.nil? }
|
322
284
|
end
|
323
285
|
|
324
286
|
# Returns the data object that stores values for +name+.
|
325
287
|
def find_obj_for_name(name)
|
326
|
-
@
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
288
|
+
idx = @field_names.index(name)
|
289
|
+
if idx
|
290
|
+
instantiate_obj(idx)
|
291
|
+
@field_objs[idx]
|
292
|
+
else
|
293
|
+
nil
|
332
294
|
end
|
333
|
-
nil
|
334
295
|
end
|
335
296
|
|
336
297
|
def offset_of(field)
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
offset = offset.ceil
|
345
|
-
end
|
346
|
-
offset += this_offset
|
347
|
-
elsif obj.field_names.include?(field_name)
|
348
|
-
this_offset = obj.offset_of(field)
|
298
|
+
idx = @field_names.index(field.to_s)
|
299
|
+
if idx
|
300
|
+
instantiate_all
|
301
|
+
|
302
|
+
offset = 0
|
303
|
+
(0...idx).each do |i|
|
304
|
+
this_offset = @field_objs[i].do_num_bytes
|
349
305
|
if ::Float === offset and ::Integer === this_offset
|
350
306
|
offset = offset.ceil
|
351
307
|
end
|
352
308
|
offset += this_offset
|
353
|
-
break
|
354
309
|
end
|
310
|
+
offset
|
311
|
+
else
|
312
|
+
nil
|
355
313
|
end
|
356
|
-
offset
|
357
314
|
end
|
358
315
|
|
359
316
|
# Override to include field names
|
@@ -387,14 +344,29 @@ module BinData
|
|
387
344
|
#---------------
|
388
345
|
private
|
389
346
|
|
347
|
+
# Instantiates all fields.
|
348
|
+
def instantiate_all
|
349
|
+
(0...@field_names.length).each { |idx| instantiate_obj(idx) }
|
350
|
+
end
|
351
|
+
|
352
|
+
# Instantiates the field object at position +idx+.
|
353
|
+
def instantiate_obj(idx)
|
354
|
+
if @field_objs[idx].nil?
|
355
|
+
fklass, fname, fparams = param(:fields)[idx]
|
356
|
+
@field_objs[idx] = fklass.new(fparams, create_env)
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
390
360
|
# Reads the values for all fields in this object from +io+.
|
391
361
|
def _do_read(io)
|
392
|
-
|
362
|
+
instantiate_all
|
363
|
+
@field_objs.each { |f| f.do_read(io) }
|
393
364
|
end
|
394
365
|
|
395
366
|
# Writes the values for all fields in this object to +io+.
|
396
367
|
def _do_write(io)
|
397
|
-
|
368
|
+
instantiate_all
|
369
|
+
@field_objs.each { |f| f.do_write(io) }
|
398
370
|
end
|
399
371
|
|
400
372
|
# Returns the number of bytes it will take to write the field represented
|
@@ -402,15 +374,22 @@ module BinData
|
|
402
374
|
# to write all fields.
|
403
375
|
def _do_num_bytes(name)
|
404
376
|
if name.nil?
|
405
|
-
|
377
|
+
instantiate_all
|
378
|
+
(@field_objs.inject(0) { |sum, f| sum + f.do_num_bytes }).ceil
|
406
379
|
else
|
407
|
-
find_obj_for_name(name.to_s)
|
380
|
+
obj = find_obj_for_name(name.to_s)
|
381
|
+
obj.nil? ? 0 : obj.do_num_bytes
|
408
382
|
end
|
409
383
|
end
|
410
384
|
|
411
|
-
# Returns a
|
412
|
-
def
|
413
|
-
|
385
|
+
# Returns a snapshot of this struct as a hash.
|
386
|
+
def _snapshot
|
387
|
+
hash = Snapshot.new
|
388
|
+
field_names.each do |name|
|
389
|
+
ss = find_obj_for_name(name).snapshot
|
390
|
+
hash[name] = ss unless ss.nil?
|
391
|
+
end
|
392
|
+
hash
|
414
393
|
end
|
415
394
|
end
|
416
395
|
end
|