bindata 0.8.1 → 0.9.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.
- data/ChangeLog +11 -2
- data/README +55 -19
- data/TODO +4 -0
- data/examples/gzip.rb +3 -3
- data/lib/bindata.rb +8 -1
- data/lib/bindata/array.rb +58 -26
- data/lib/bindata/base.rb +133 -83
- data/lib/bindata/choice.rb +142 -61
- data/lib/bindata/float.rb +4 -0
- data/lib/bindata/int.rb +14 -0
- data/lib/bindata/lazy.rb +19 -2
- data/lib/bindata/multi_value.rb +144 -0
- data/lib/bindata/registry.rb +13 -8
- data/lib/bindata/rest.rb +41 -0
- data/lib/bindata/sanitize.rb +36 -0
- data/lib/bindata/single.rb +31 -6
- data/lib/bindata/single_value.rb +203 -0
- data/lib/bindata/string.rb +57 -32
- data/lib/bindata/stringz.rb +15 -0
- data/lib/bindata/struct.rb +189 -204
- data/spec/array_spec.rb +66 -42
- data/spec/base_spec.rb +202 -80
- data/spec/choice_spec.rb +172 -51
- data/spec/int_spec.rb +5 -5
- data/spec/lazy_spec.rb +26 -23
- data/spec/multi_value_spec.rb +250 -0
- data/spec/registry_spec.rb +8 -8
- data/spec/rest_spec.rb +30 -0
- data/spec/sanitize_spec.rb +108 -0
- data/spec/single_spec.rb +63 -49
- data/spec/single_value_spec.rb +131 -0
- data/spec/string_spec.rb +47 -47
- data/spec/stringz_spec.rb +29 -29
- data/spec/struct_spec.rb +112 -198
- metadata +58 -57
data/lib/bindata/choice.rb
CHANGED
@@ -1,29 +1,54 @@
|
|
1
|
+
require 'forwardable'
|
1
2
|
require 'bindata/base'
|
3
|
+
require 'bindata/sanitize'
|
2
4
|
|
3
5
|
module BinData
|
4
6
|
# A Choice is a collection of data objects of which only one is active
|
5
7
|
# at any particular time.
|
6
8
|
#
|
7
9
|
# require 'bindata'
|
8
|
-
# require 'stringio'
|
9
10
|
#
|
10
|
-
#
|
11
|
+
# type1 = [:string, {:value => "Type1"}]
|
12
|
+
# type2 = [:string, {:value => "Type2"}]
|
13
|
+
#
|
14
|
+
# choices = [ type1, type2 ]
|
11
15
|
# a = BinData::Choice.new(:choices => choices, :selection => 1)
|
12
|
-
# a.value # =>
|
16
|
+
# a.value # => "Type2"
|
17
|
+
#
|
18
|
+
# choices = [ nil, nil, nil, type1, nil, type2 ]
|
19
|
+
# a = BinData::Choice.new(:choices => choices, :selection => 3)
|
20
|
+
# a.value # => "Type1"
|
21
|
+
#
|
22
|
+
# choices = {5 => type1, 17 => type2}
|
23
|
+
# a = BinData::Choice.new(:choices => choices, :selection => 5)
|
24
|
+
# a.value # => "Type1"
|
25
|
+
#
|
26
|
+
# mychoice = 'big'
|
27
|
+
# choices = {'big' => :uint16be, 'little' => :uint16le}
|
28
|
+
# a = BinData::Choice.new(:choices => choices,
|
29
|
+
# :selection => lambda { mychoice })
|
30
|
+
# a.value = 256
|
31
|
+
# a.to_s #=> "\001\000"
|
32
|
+
# mychoice[0..-1] = 'little'
|
33
|
+
# a.to_s #=> "\000\001"
|
34
|
+
#
|
13
35
|
#
|
14
36
|
# == Parameters
|
15
37
|
#
|
16
38
|
# Parameters may be provided at initialisation to control the behaviour of
|
17
39
|
# an object. These params are:
|
18
40
|
#
|
19
|
-
# <tt>:choices</tt>::
|
20
|
-
# The format of the array is
|
21
|
-
# representing the data object type.
|
22
|
-
# is to have params passed to it, then it
|
23
|
-
# provided as [type_symbol, hash_params].
|
24
|
-
#
|
25
|
-
#
|
26
|
-
|
41
|
+
# <tt>:choices</tt>:: Either an array or a hash specifying the possible
|
42
|
+
# data objects. The format of the array/hash.values is
|
43
|
+
# a list of symbols representing the data object type.
|
44
|
+
# If a choice is to have params passed to it, then it
|
45
|
+
# should be provided as [type_symbol, hash_params].
|
46
|
+
# An implementation gotcha is that the hash may not
|
47
|
+
# contain symbols as keys.
|
48
|
+
# <tt>:selection</tt>:: An index/key into the :choices array/hash which
|
49
|
+
# specifies the currently active choice.
|
50
|
+
class Choice < BinData::Base
|
51
|
+
extend Forwardable
|
27
52
|
|
28
53
|
# Register this class
|
29
54
|
register(self.name, self)
|
@@ -31,61 +56,93 @@ module BinData
|
|
31
56
|
# These are the parameters used by this class.
|
32
57
|
mandatory_parameters :choices, :selection
|
33
58
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
#
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
59
|
+
class << self
|
60
|
+
|
61
|
+
# Returns a sanitized +params+ that is of the form expected
|
62
|
+
# by #initialize.
|
63
|
+
def sanitize_parameters(params, endian = nil)
|
64
|
+
params = params.dup
|
65
|
+
|
66
|
+
if params.has_key?(:choices)
|
67
|
+
choices = params[:choices]
|
68
|
+
|
69
|
+
case choices
|
70
|
+
when ::Hash
|
71
|
+
new_choices = {}
|
72
|
+
choices.keys.each do |key|
|
73
|
+
# ensure valid hash keys
|
74
|
+
if Symbol === key
|
75
|
+
msg = ":choices hash may not have symbols for keys"
|
76
|
+
raise ArgumentError, msg
|
77
|
+
elsif key.nil?
|
78
|
+
raise ArgumentError, ":choices hash may not have nil key"
|
79
|
+
end
|
80
|
+
|
81
|
+
# collect sanitized choice values
|
82
|
+
type, param = choices[key]
|
83
|
+
klass = lookup(type, endian)
|
84
|
+
if klass.nil?
|
85
|
+
raise TypeError, "unknown type '#{type}' for #{self}"
|
86
|
+
end
|
87
|
+
val = [klass, SanitizedParameters.new(klass, param, endian)]
|
88
|
+
new_choices[key] = val
|
89
|
+
end
|
90
|
+
params[:choices] = new_choices
|
91
|
+
when ::Array
|
92
|
+
choices.collect! do |type, param|
|
93
|
+
if type.nil?
|
94
|
+
# allow sparse arrays
|
95
|
+
nil
|
96
|
+
else
|
97
|
+
klass = lookup(type, endian)
|
98
|
+
if klass.nil?
|
99
|
+
raise TypeError, "unknown type '#{type}' for #{self}"
|
100
|
+
end
|
101
|
+
[klass, SanitizedParameters.new(klass, param, endian)]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
params[:choices] = choices
|
105
|
+
else
|
106
|
+
raise ArgumentError, "unknown type for :choices (#{choices.class})"
|
107
|
+
end
|
44
108
|
end
|
45
|
-
@choices << klass.new(choice_params, create_env)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
# Resets the internal state to that of a newly created object.
|
50
|
-
def clear
|
51
|
-
the_choice.clear
|
52
|
-
end
|
53
109
|
|
54
|
-
|
55
|
-
|
56
|
-
the_choice.clear?
|
57
|
-
end
|
58
|
-
|
59
|
-
# Reads the value of the selected data object from +io+.
|
60
|
-
def _do_read(io)
|
61
|
-
the_choice.do_read(io)
|
62
|
-
end
|
110
|
+
super(params, endian)
|
111
|
+
end
|
63
112
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
113
|
+
# Returns all the possible field names a :choice may have.
|
114
|
+
def all_possible_field_names(sanitized_params)
|
115
|
+
unless SanitizedParameters === sanitized_params
|
116
|
+
raise ArgumentError, "parameters aren't sanitized"
|
117
|
+
end
|
68
118
|
|
69
|
-
|
70
|
-
|
71
|
-
|
119
|
+
choices = sanitized_params[:choices]
|
120
|
+
|
121
|
+
names = []
|
122
|
+
if ::Array === choices
|
123
|
+
choices.each do |cklass, cparams|
|
124
|
+
names.concat(cklass.all_possible_field_names(cparams))
|
125
|
+
end
|
126
|
+
elsif ::Hash === choices
|
127
|
+
choices.values.each do |cklass, cparams|
|
128
|
+
names.concat(cklass.all_possible_field_names(cparams))
|
129
|
+
end
|
130
|
+
end
|
131
|
+
names
|
132
|
+
end
|
72
133
|
end
|
73
134
|
|
74
|
-
|
75
|
-
|
76
|
-
def _num_bytes(what)
|
77
|
-
the_choice.num_bytes(what)
|
78
|
-
end
|
135
|
+
def initialize(params = {}, env = nil)
|
136
|
+
super(params, env)
|
79
137
|
|
80
|
-
|
81
|
-
|
82
|
-
|
138
|
+
# prepare collection of instantiated choice objects
|
139
|
+
@choices = (param(:choices) === ::Array) ? [] : {}
|
140
|
+
@last_key = nil
|
83
141
|
end
|
84
142
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
end
|
143
|
+
def_delegators :the_choice, :clear, :clear?, :_do_read, :done_read
|
144
|
+
def_delegators :the_choice, :_write, :_num_bytes, :snapshot
|
145
|
+
def_delegators :the_choice, :single_value?, :field_names
|
89
146
|
|
90
147
|
# Returns the data object that stores values for +name+.
|
91
148
|
def find_obj_for_name(name)
|
@@ -110,11 +167,35 @@ module BinData
|
|
110
167
|
|
111
168
|
# Returns the selected data object.
|
112
169
|
def the_choice
|
113
|
-
|
114
|
-
|
115
|
-
|
170
|
+
key = eval_param(:selection)
|
171
|
+
|
172
|
+
if key.nil?
|
173
|
+
raise IndexError, ":selection returned nil value"
|
174
|
+
end
|
175
|
+
|
176
|
+
obj = @choices[key]
|
177
|
+
if obj.nil?
|
178
|
+
# instantiate choice object
|
179
|
+
choice_klass, choice_params = param(:choices)[key]
|
180
|
+
if choice_klass.nil?
|
181
|
+
raise IndexError, "selection #{key} does not exist in :choices"
|
182
|
+
end
|
183
|
+
obj = choice_klass.new(choice_params, create_env)
|
184
|
+
@choices[key] = obj
|
116
185
|
end
|
117
|
-
|
186
|
+
|
187
|
+
# for single_values copy the value when the selected object changes
|
188
|
+
if key != @last_key
|
189
|
+
if @last_key != nil
|
190
|
+
prev = @choices[@last_key]
|
191
|
+
if prev != nil and prev.single_value? and obj.single_value?
|
192
|
+
obj.value = prev.value
|
193
|
+
end
|
194
|
+
end
|
195
|
+
@last_key = key
|
196
|
+
end
|
197
|
+
|
198
|
+
obj
|
118
199
|
end
|
119
200
|
end
|
120
201
|
end
|
data/lib/bindata/float.rb
CHANGED
@@ -64,21 +64,25 @@ module BinData
|
|
64
64
|
|
65
65
|
# Single precision floating point number in little endian format
|
66
66
|
class FloatLe < Single
|
67
|
+
register(self.name, self)
|
67
68
|
Float.create_float_methods(self, true, :little)
|
68
69
|
end
|
69
70
|
|
70
71
|
# Single precision floating point number in big endian format
|
71
72
|
class FloatBe < Single
|
73
|
+
register(self.name, self)
|
72
74
|
Float.create_float_methods(self, true, :big)
|
73
75
|
end
|
74
76
|
|
75
77
|
# Double precision floating point number in little endian format
|
76
78
|
class DoubleLe < Single
|
79
|
+
register(self.name, self)
|
77
80
|
Float.create_float_methods(self, false, :little)
|
78
81
|
end
|
79
82
|
|
80
83
|
# Double precision floating point number in big endian format
|
81
84
|
class DoubleBe < Single
|
85
|
+
register(self.name, self)
|
82
86
|
Float.create_float_methods(self, false, :big)
|
83
87
|
end
|
84
88
|
end
|
data/lib/bindata/int.rb
CHANGED
@@ -101,71 +101,85 @@ module BinData
|
|
101
101
|
|
102
102
|
# Unsigned 1 byte integer.
|
103
103
|
class Uint8 < Single
|
104
|
+
register(self.name, self)
|
104
105
|
Integer.create_uint_methods(self, 8, :little)
|
105
106
|
end
|
106
107
|
|
107
108
|
# Unsigned 2 byte little endian integer.
|
108
109
|
class Uint16le < Single
|
110
|
+
register(self.name, self)
|
109
111
|
Integer.create_uint_methods(self, 16, :little)
|
110
112
|
end
|
111
113
|
|
112
114
|
# Unsigned 2 byte big endian integer.
|
113
115
|
class Uint16be < Single
|
116
|
+
register(self.name, self)
|
114
117
|
Integer.create_uint_methods(self, 16, :big)
|
115
118
|
end
|
116
119
|
|
117
120
|
# Unsigned 4 byte little endian integer.
|
118
121
|
class Uint32le < Single
|
122
|
+
register(self.name, self)
|
119
123
|
Integer.create_uint_methods(self, 32, :little)
|
120
124
|
end
|
121
125
|
|
122
126
|
# Unsigned 4 byte big endian integer.
|
123
127
|
class Uint32be < Single
|
128
|
+
register(self.name, self)
|
124
129
|
Integer.create_uint_methods(self, 32, :big)
|
125
130
|
end
|
126
131
|
|
127
132
|
# Unsigned 8 byte little endian integer.
|
128
133
|
class Uint64le < Single
|
134
|
+
register(self.name, self)
|
129
135
|
Integer.create_uint_methods(self, 64, :little)
|
130
136
|
end
|
131
137
|
|
132
138
|
# Unsigned 8 byte big endian integer.
|
133
139
|
class Uint64be < Single
|
140
|
+
register(self.name, self)
|
134
141
|
Integer.create_uint_methods(self, 64, :big)
|
135
142
|
end
|
136
143
|
|
137
144
|
# Signed 1 byte integer.
|
138
145
|
class Int8 < Single
|
146
|
+
register(self.name, self)
|
139
147
|
Integer.create_int_methods(self, 8, :little)
|
140
148
|
end
|
141
149
|
|
142
150
|
# Signed 2 byte little endian integer.
|
143
151
|
class Int16le < Single
|
152
|
+
register(self.name, self)
|
144
153
|
Integer.create_int_methods(self, 16, :little)
|
145
154
|
end
|
146
155
|
|
147
156
|
# Signed 2 byte big endian integer.
|
148
157
|
class Int16be < Single
|
158
|
+
register(self.name, self)
|
149
159
|
Integer.create_int_methods(self, 16, :big)
|
150
160
|
end
|
151
161
|
|
152
162
|
# Signed 4 byte little endian integer.
|
153
163
|
class Int32le < Single
|
164
|
+
register(self.name, self)
|
154
165
|
Integer.create_int_methods(self, 32, :little)
|
155
166
|
end
|
156
167
|
|
157
168
|
# Signed 4 byte big endian integer.
|
158
169
|
class Int32be < Single
|
170
|
+
register(self.name, self)
|
159
171
|
Integer.create_int_methods(self, 32, :big)
|
160
172
|
end
|
161
173
|
|
162
174
|
# Signed 8 byte little endian integer.
|
163
175
|
class Int64le < Single
|
176
|
+
register(self.name, self)
|
164
177
|
Integer.create_int_methods(self, 64, :little)
|
165
178
|
end
|
166
179
|
|
167
180
|
# Signed 8 byte big endian integer.
|
168
181
|
class Int64be < Single
|
182
|
+
register(self.name, self)
|
169
183
|
Integer.create_int_methods(self, 64, :big)
|
170
184
|
end
|
171
185
|
end
|
data/lib/bindata/lazy.rb
CHANGED
@@ -16,6 +16,8 @@ module BinData
|
|
16
16
|
# An empty hash shared by all instances
|
17
17
|
@@empty_hash = Hash.new.freeze
|
18
18
|
|
19
|
+
@@variables_cache = {}
|
20
|
+
|
19
21
|
# Creates a new environment. +parent+ is the environment of the
|
20
22
|
# parent data object.
|
21
23
|
def initialize(parent = nil)
|
@@ -39,10 +41,25 @@ module BinData
|
|
39
41
|
# will be accessible as a variable for any lambda evaluated
|
40
42
|
# with #lazy_eval.
|
41
43
|
def add_variable(sym, value)
|
44
|
+
sym = sym.to_sym
|
42
45
|
if @variables.equal?(@@empty_hash)
|
43
|
-
|
46
|
+
# optimise the case where only 1 variable is added as this
|
47
|
+
# is the most common occurance (BinData::Arrays adding index)
|
48
|
+
key = [sym, value]
|
49
|
+
@variables = @@variables_cache[key]
|
50
|
+
if @variables.nil?
|
51
|
+
# cache this variable and value so it can be shared with
|
52
|
+
# other LazyEvalEnvs to keep memory usage down
|
53
|
+
@variables = {sym => value}.freeze
|
54
|
+
@@variables_cache[key] = @variables
|
55
|
+
end
|
56
|
+
else
|
57
|
+
if @variables.length == 1
|
58
|
+
key = @variables.keys[0]
|
59
|
+
@variables = {key => @variables[key]}
|
60
|
+
end
|
61
|
+
@variables[sym] = value
|
44
62
|
end
|
45
|
-
@variables[sym.to_sym] = value
|
46
63
|
end
|
47
64
|
|
48
65
|
# TODO: offset_of needs to be better thought out
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'bindata/struct'
|
2
|
+
|
3
|
+
module BinData
|
4
|
+
# A MultiValue is a declarative wrapper around Struct.
|
5
|
+
#
|
6
|
+
# require 'bindata'
|
7
|
+
#
|
8
|
+
# class Tuple < BinData::MultiValue
|
9
|
+
# int8 :x
|
10
|
+
# int8 :y
|
11
|
+
# int8 :z
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# class SomeDataType < BinData::MultiValue
|
15
|
+
# hide 'a'
|
16
|
+
#
|
17
|
+
# int32le :a
|
18
|
+
# int16le :b
|
19
|
+
# tuple nil
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# obj = SomeDataType.new
|
23
|
+
# obj.field_names =># ["b", "x", "y", "z"]
|
24
|
+
#
|
25
|
+
#
|
26
|
+
# == Parameters
|
27
|
+
#
|
28
|
+
# Parameters may be provided at initialisation to control the behaviour of
|
29
|
+
# an object. These params are:
|
30
|
+
#
|
31
|
+
# <tt>:fields</tt>:: An array specifying the fields for this struct.
|
32
|
+
# Each element of the array is of the form [type, name,
|
33
|
+
# params]. Type is a symbol representing a registered
|
34
|
+
# type. Name is the name of this field. Name may be
|
35
|
+
# nil as in the example above. Params is an optional
|
36
|
+
# hash of parameters to pass to this field when
|
37
|
+
# instantiating it.
|
38
|
+
# <tt>:hide</tt>:: A list of the names of fields that are to be hidden
|
39
|
+
# from the outside world. Hidden fields don't appear
|
40
|
+
# in #snapshot or #field_names but are still accessible
|
41
|
+
# by name.
|
42
|
+
# <tt>:endian</tt>:: Either :little or :big. This specifies the default
|
43
|
+
# endian of any numerics in this struct, or in any
|
44
|
+
# nested data objects.
|
45
|
+
class MultiValue < Struct
|
46
|
+
|
47
|
+
class << self
|
48
|
+
# Register the names of all subclasses of this class.
|
49
|
+
def inherited(subclass) #:nodoc:
|
50
|
+
register(subclass.name, subclass)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns or sets the endianess of numerics used in this stucture.
|
54
|
+
# Endianess is applied to the fields of this structure.
|
55
|
+
# Valid values are :little and :big.
|
56
|
+
def endian(endian = nil)
|
57
|
+
@endian ||= nil
|
58
|
+
if [:little, :big].include?(endian)
|
59
|
+
@endian = endian
|
60
|
+
elsif endian != nil
|
61
|
+
raise ArgumentError, "unknown value for endian '#{endian}'"
|
62
|
+
end
|
63
|
+
@endian
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the names of any hidden fields in this struct. Any given args
|
67
|
+
# are appended to the hidden list.
|
68
|
+
def hide(*args)
|
69
|
+
# note that fields are stored in an instance variable not a class var
|
70
|
+
@hide ||= []
|
71
|
+
args.each do |name|
|
72
|
+
next if name.nil?
|
73
|
+
@hide << name.to_s
|
74
|
+
end
|
75
|
+
@hide
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns all stored fields. Should only be called by #sanitize_parameters
|
79
|
+
def fields
|
80
|
+
@fields || []
|
81
|
+
end
|
82
|
+
|
83
|
+
# Used to define fields for this structure.
|
84
|
+
def method_missing(symbol, *args)
|
85
|
+
name, params = args
|
86
|
+
|
87
|
+
type = symbol
|
88
|
+
name = (name.nil? or name == "") ? nil : name.to_s
|
89
|
+
params ||= {}
|
90
|
+
|
91
|
+
# note that fields are stored in an instance variable not a class var
|
92
|
+
@fields ||= []
|
93
|
+
|
94
|
+
# check that type is known
|
95
|
+
if lookup(type, endian).nil?
|
96
|
+
raise TypeError, "unknown type '#{type}' for #{self}", caller
|
97
|
+
end
|
98
|
+
|
99
|
+
# check that name is okay
|
100
|
+
if name != nil
|
101
|
+
# check for duplicate names
|
102
|
+
@fields.each do |t, n, p|
|
103
|
+
if n == name
|
104
|
+
raise SyntaxError, "duplicate field '#{name}' in #{self}", caller
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# check that name doesn't shadow an existing method
|
109
|
+
if self.instance_methods.include?(name)
|
110
|
+
raise NameError.new("", name),
|
111
|
+
"field '#{name}' shadows an existing method", caller
|
112
|
+
end
|
113
|
+
|
114
|
+
# check that name isn't reserved
|
115
|
+
if ::Hash.instance_methods.include?(name)
|
116
|
+
raise NameError.new("", name),
|
117
|
+
"field '#{name}' is a reserved name", caller
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# remember this field. These fields will be recalled upon creating
|
122
|
+
# an instance of this class
|
123
|
+
@fields.push([type, name, params])
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns a sanitized +params+ that is of the form expected
|
127
|
+
# by #initialize.
|
128
|
+
def sanitize_parameters(params, endian = nil)
|
129
|
+
params = params.dup
|
130
|
+
|
131
|
+
# possibly override endian
|
132
|
+
endian = params[:endian] || self.endian || endian
|
133
|
+
unless endian.nil?
|
134
|
+
params[:endian] = endian
|
135
|
+
end
|
136
|
+
|
137
|
+
params[:fields] = params[:fields] || self.fields
|
138
|
+
params[:hide] = params[:hide] || self.hide
|
139
|
+
|
140
|
+
super(params, endian)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|