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.

@@ -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
- # choices = [ [:int8, {:value => 3}], [:int8, {:value => 5}] ]
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 # => 5
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>:: An array specifying the possible data objects.
20
- # The format of the array is a list of symbols
21
- # representing the data object type. If a choice
22
- # is to have params passed to it, then it should be
23
- # provided as [type_symbol, hash_params].
24
- # <tt>:selection</tt>:: An index into the :choices array which specifies
25
- # the currently active choice.
26
- class Choice < Base
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
- def initialize(params = {}, env = nil)
35
- super(params, env)
36
-
37
- # instantiate all choices
38
- @choices = []
39
- param(:choices).each do |choice_type, choice_params|
40
- choice_params ||= {}
41
- klass = klass_lookup(choice_type)
42
- if klass.nil?
43
- raise TypeError, "unknown type '#{choice_type.id2name}' for #{self}"
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
- # Returns if the selected data object is clear?.
55
- def clear?
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
- # To be called after calling #do_read.
65
- def done_read
66
- the_choice.done_read
67
- end
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
- # Writes the value of the selected data object to +io+.
70
- def _write(io)
71
- the_choice.write(io)
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
- # Returns the number of bytes it will take to write the
75
- # selected data object.
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
- # Returns a snapshot of the selected data object.
81
- def snapshot
82
- the_choice.snapshot
138
+ # prepare collection of instantiated choice objects
139
+ @choices = (param(:choices) === ::Array) ? [] : {}
140
+ @last_key = nil
83
141
  end
84
142
 
85
- # Returns a list of the names of all fields of the selected data object.
86
- def field_names
87
- the_choice.field_names
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
- index = eval_param(:selection)
114
- if index < 0 or index >= @choices.length
115
- raise IndexError, "selection #{index} is out of range"
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
- @choices[index]
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
@@ -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
@@ -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
@@ -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
- @variables = {}
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