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.

@@ -5,17 +5,6 @@ module BinData
5
5
  # All methods provided by the framework are to be implemented or overridden
6
6
  # by subclasses of BinData::Base.
7
7
  module Framework
8
- def self.included(base) #:nodoc:
9
- base.extend ClassMethods
10
- end
11
-
12
- module ClassMethods #:nodoc:
13
- # Performs sanity checks on the given parameters. This method converts
14
- # the parameters to the form expected by this data object.
15
- def sanitize_parameters!(parameters)
16
- end
17
- end
18
-
19
8
  # Initializes the state of the object. All instance variables that
20
9
  # are used by the object must be initialized here.
21
10
  def initialize_instance
@@ -6,8 +6,7 @@ module BinData
6
6
 
7
7
  module Int #:nodoc: all
8
8
  class << self
9
- def define_class(nbits, endian, signed)
10
- name = class_name(nbits, endian, signed)
9
+ def define_class(name, nbits, endian, signed)
11
10
  unless BinData.const_defined?(name)
12
11
  BinData.module_eval <<-END
13
12
  class #{name} < BinData::BasePrimitive
@@ -19,13 +18,6 @@ module BinData
19
18
  BinData.const_get(name)
20
19
  end
21
20
 
22
- def class_name(nbits, endian, signed)
23
- endian_str = (endian == :big) ? "be" : "le"
24
- base = (signed == :signed) ? "Int" : "Uint"
25
-
26
- "#{base}#{nbits}#{endian_str}"
27
- end
28
-
29
21
  def define_methods(int_class, nbits, endian, signed)
30
22
  raise "nbits must be divisible by 8" unless (nbits % 8).zero?
31
23
 
@@ -48,14 +40,11 @@ module BinData
48
40
 
49
41
  def value_to_binary_string(val)
50
42
  #{create_clamp_code(nbits, signed)}
51
- #{create_int2uint_code(nbits) if signed == :signed}
52
- #{create_to_binary_s_code(nbits, endian)}
43
+ #{create_to_binary_s_code(nbits, endian, signed)}
53
44
  end
54
45
 
55
46
  def read_and_return_value(io)
56
- val = #{create_read_code(nbits, endian)}
57
- #{create_uint2int_code(nbits) if signed == :signed}
58
- val
47
+ #{create_read_code(nbits, endian, signed)}
59
48
  end
60
49
  END
61
50
  end
@@ -68,79 +57,115 @@ module BinData
68
57
  max = (1 << (nbits - 1)) - 1
69
58
  min = -(max + 1)
70
59
  else
71
- min = 0
72
60
  max = (1 << nbits) - 1
61
+ min = 0
73
62
  end
74
63
 
75
64
  "val = (val < #{min}) ? #{min} : (val > #{max}) ? #{max} : val"
76
65
  end
77
66
 
78
- def create_int2uint_code(nbits)
79
- "val = val & #{(1 << nbits) - 1}"
67
+ def create_read_code(nbits, endian, signed)
68
+ unpack_str = create_read_unpack_code(nbits, endian, signed)
69
+ assemble_str = create_read_assemble_code(nbits, endian, signed)
70
+
71
+ read_str = "(#{unpack_str} ; #{assemble_str})"
72
+
73
+ if need_conversion_code?(nbits, signed)
74
+ "val = #{read_str} ; #{create_uint2int_code(nbits)}"
75
+ else
76
+ read_str
77
+ end
80
78
  end
81
79
 
82
- def create_uint2int_code(nbits)
83
- "val = val - #{1 << nbits} if (val >= #{1 << (nbits - 1)})"
80
+ def create_read_unpack_code(nbits, endian, signed)
81
+ nbytes = nbits / 8
82
+
83
+ "ints = io.readbytes(#{nbytes}).unpack('#{pack_directive(nbits, endian, signed)}')"
84
84
  end
85
85
 
86
- def create_read_code(nbits, endian)
86
+ def create_read_assemble_code(nbits, endian, signed)
87
87
  bits_per_word = bytes_per_word(nbits) * 8
88
88
  nwords = nbits / bits_per_word
89
- nbytes = nbits / 8
90
89
 
91
90
  idx = (0 ... nwords).to_a
92
91
  idx.reverse! if (endian == :big)
93
92
 
94
93
  parts = (0 ... nwords).collect do |i|
95
94
  if i.zero?
96
- "a.at(#{idx[i]})"
95
+ "ints.at(#{idx[i]})"
97
96
  else
98
- "(a.at(#{idx[i]}) << #{bits_per_word * i})"
97
+ "(ints.at(#{idx[i]}) << #{bits_per_word * i})"
99
98
  end
100
99
  end
101
100
 
102
- unpack_str = "a = io.readbytes(#{nbytes}).unpack('#{pack_directive(nbits, endian)}')"
103
101
  assemble_str = parts.join(" + ")
104
-
105
- "(#{unpack_str}; #{assemble_str})"
106
102
  end
107
103
 
108
- def create_to_binary_s_code(nbits, endian)
104
+ def create_to_binary_s_code(nbits, endian, signed)
109
105
  # special case 8bit integers for speed
110
- return "val.chr" if nbits == 8
106
+ return "(val & 0xff).chr" if nbits == 8
111
107
 
112
108
  bits_per_word = bytes_per_word(nbits) * 8
113
109
  nwords = nbits / bits_per_word
114
110
  mask = (1 << bits_per_word) - 1
115
111
 
116
112
  vals = (0 ... nwords).collect do |i|
117
- i.zero? ? "val" : "(val >> #{bits_per_word * i})"
113
+ i.zero? ? "val" : "val >> #{bits_per_word * i}"
118
114
  end
119
115
  vals.reverse! if (endian == :big)
120
116
 
121
- parts = (0 ... nwords).collect { |i| "#{vals[i]} & #{mask}" }
122
- array_str = "[" + parts.join(", ") + "]"
117
+ array_str = "[" + vals.collect { |val| "#{val} & #{mask}" }.join(", ") + "]" # TODO: "& mask" is needed to work around jruby bug
118
+ pack_str = "#{array_str}.pack('#{pack_directive(nbits, endian, signed)}')"
123
119
 
124
- "#{array_str}.pack('#{pack_directive(nbits, endian)}')"
120
+ if need_conversion_code?(nbits, signed)
121
+ "#{create_int2uint_code(nbits)} ; #{pack_str}"
122
+ else
123
+ pack_str
124
+ end
125
+ end
126
+
127
+ def create_int2uint_code(nbits)
128
+ "val &= #{(1 << nbits) - 1}"
129
+ end
130
+
131
+ def create_uint2int_code(nbits)
132
+ "(val >= #{1 << (nbits - 1)}) ? val - #{1 << nbits} : val"
125
133
  end
126
134
 
127
135
  def bytes_per_word(nbits)
128
- (nbits % 32).zero? ? 4 : (nbits % 16).zero? ? 2 : 1
136
+ (nbits % 64).zero? ? 8 :
137
+ (nbits % 32).zero? ? 4 :
138
+ (nbits % 16).zero? ? 2 :
139
+ 1
129
140
  end
130
141
 
131
- def pack_directive(nbits, endian)
142
+ def pack_directive(nbits, endian, signed)
132
143
  bits_per_word = bytes_per_word(nbits) * 8
133
144
  nwords = nbits / bits_per_word
134
145
 
135
- if (nbits % 32).zero?
136
- d = (endian == :big) ? 'N' : 'V'
146
+ if (nbits % 64).zero?
147
+ d = (endian == :big) ? 'Q>' : 'Q<'
148
+ elsif (nbits % 32).zero?
149
+ d = (endian == :big) ? 'L>' : 'L<'
137
150
  elsif (nbits % 16).zero?
138
- d = (endian == :big) ? 'n' : 'v'
151
+ d = (endian == :big) ? 'S>' : 'S<'
139
152
  else
140
153
  d = 'C'
141
154
  end
142
155
 
143
- d * nwords
156
+ if pack_directive_signed?(nbits, signed)
157
+ (d * nwords).downcase
158
+ else
159
+ d * nwords
160
+ end
161
+ end
162
+
163
+ def need_conversion_code?(nbits, signed)
164
+ signed == :signed and not pack_directive_signed?(nbits, signed)
165
+ end
166
+
167
+ def pack_directive_signed?(nbits, signed)
168
+ signed == :signed and [64, 32, 16, 8].include?(nbits)
144
169
  end
145
170
  end
146
171
  end
@@ -160,17 +185,17 @@ module BinData
160
185
  module IntFactory
161
186
  def const_missing(name)
162
187
  mappings = {
163
- /^Uint(\d+)be$/ => [:big, :unsigned],
188
+ /^Uint(\d+)be$/ => [:big, :unsigned],
164
189
  /^Uint(\d+)le$/ => [:little, :unsigned],
165
- /^Int(\d+)be$/ => [:big, :signed],
166
- /^Int(\d+)le$/ => [:little, :signed],
190
+ /^Int(\d+)be$/ => [:big, :signed],
191
+ /^Int(\d+)le$/ => [:little, :signed],
167
192
  }
168
193
 
169
194
  mappings.each_pair do |regex, args|
170
195
  if regex =~ name.to_s
171
196
  nbits = $1.to_i
172
197
  if (nbits % 8).zero?
173
- return Int.define_class(nbits, *args)
198
+ return Int.define_class(name, nbits, *args)
174
199
  end
175
200
  end
176
201
  end
@@ -187,8 +187,8 @@ module BinData
187
187
 
188
188
  # Use #seek and #pos on seekable streams
189
189
  module SeekableStream
190
- # Returns the current offset of the io stream. The exact value of
191
- # the offset when reading bitfields is not defined.
190
+ # Returns the current offset of the io stream. Offset will be rounded
191
+ # up when reading bitfields.
192
192
  def offset
193
193
  raw_io.pos - @initial_pos
194
194
  end
@@ -222,8 +222,8 @@ module BinData
222
222
 
223
223
  # Manually keep track of offset for unseekable streams.
224
224
  module UnSeekableStream
225
- # Returns the current offset of the io stream. The exact value of
226
- # the offset when reading bitfields is not defined.
225
+ # Returns the current offset of the io stream. Offset will be rounded
226
+ # up when reading bitfields.
227
227
  def offset
228
228
  @read_count ||= 0
229
229
  end
@@ -281,6 +281,8 @@ module BinData
281
281
  @wval = 0
282
282
  @wendian = nil
283
283
 
284
+ @write_count = 0
285
+
284
286
  @bytes_remaining = nil
285
287
  end
286
288
 
@@ -304,6 +306,12 @@ module BinData
304
306
  end
305
307
  end
306
308
 
309
+ # Returns the current offset of the io stream. Offset will be rounded
310
+ # up when writing bitfields.
311
+ def offset
312
+ @write_count + (@wnbits > 0 ? 1 : 0)
313
+ end
314
+
307
315
  # Writes the given string of bytes to the io stream.
308
316
  def writebytes(str)
309
317
  flushbits
@@ -391,6 +399,7 @@ module BinData
391
399
  @bytes_remaining -= data.size
392
400
  end
393
401
 
402
+ @write_count += data.size
394
403
  @raw_io.write(data)
395
404
  end
396
405
 
@@ -85,7 +85,7 @@ module BinData
85
85
 
86
86
  if obj_parent.has_parameter?(symbol)
87
87
  obj_parent.get_parameter(symbol)
88
- elsif obj_parent.safe_respond_to?(symbol)
88
+ elsif obj_parent.safe_respond_to?(symbol, true)
89
89
  obj_parent.__send__(symbol, *args)
90
90
  else
91
91
  symbol
@@ -60,16 +60,11 @@ module BinData
60
60
  # Primitive objects accept all the parameters that BinData::BasePrimitive do.
61
61
  #
62
62
  class Primitive < BasePrimitive
63
- include DSLMixin
63
+ extend DSLMixin
64
64
 
65
65
  unregister_self
66
- dsl_parser :primitive
67
-
68
- class << self
69
- def sanitize_parameters!(params) #:nodoc:
70
- params[:struct_params] = params.create_sanitized_params(dsl_params, BinData::Struct)
71
- end
72
- end
66
+ dsl_parser :primitive
67
+ arg_processor :primitive
73
68
 
74
69
  mandatory_parameter :struct_params
75
70
 
@@ -138,4 +133,10 @@ module BinData
138
133
  # To be implemented by subclasses
139
134
  ###########################################################################
140
135
  end
136
+
137
+ class PrimitiveArgProcessor < BaseArgProcessor
138
+ def sanitize_parameters!(obj_class, params)
139
+ params[:struct_params] = params.create_sanitized_params(obj_class.dsl_params, BinData::Struct)
140
+ end
141
+ end
141
142
  end
@@ -1,74 +1,23 @@
1
1
  require 'bindata/dsl'
2
- require 'bindata/sanitize'
3
2
  require 'bindata/struct'
4
3
 
5
4
  module BinData
6
5
  # A Record is a declarative wrapper around Struct.
7
6
  #
8
- # require 'bindata'
9
- #
10
- # class SomeDataType < BinData::Record
11
- # hide :a
12
- #
13
- # int32le :a
14
- # int16le :b
15
- # struct :s do
16
- # int8 :x
17
- # int8 :y
18
- # int8 :z
19
- # end
20
- # end
21
- #
22
- # obj = SomeDataType.new
23
- # obj.field_names =># [:b, :s]
24
- # obj.s.field_names =># [:x, :y, :z]
25
- #
7
+ # See +Struct+ for more info.
26
8
  class Record < BinData::Struct
27
- include DSLMixin
9
+ extend DSLMixin
28
10
 
29
11
  unregister_self
30
- dsl_parser :struct
31
-
32
- class << self
33
-
34
- def arg_extractor
35
- MultiFieldArgExtractor
36
- end
37
-
38
- def sanitize_parameters!(params) #:nodoc:
39
- params.merge!(dsl_params)
40
-
41
- super(params)
42
-
43
- define_field_accessors(params[:fields].fields)
44
- end
45
-
46
- # Defines accessor methods to avoid the overhead of going through
47
- # Struct#method_missing. This is purely a speed optimisation.
48
- # Removing this method will not have any effect on correctness.
49
- def define_field_accessors(fields) #:nodoc:
50
- unless method_defined?(:bindata_defined_accessors_for_fields?)
51
- fields.each_with_index do |field, i|
52
- name = field.name_as_sym
53
- if name
54
- define_field_accessors_for(name, i)
55
- end
56
- end
12
+ dsl_parser :struct
13
+ arg_processor :record
14
+ end
57
15
 
58
- define_method(:bindata_defined_accessors_for_fields?) { true }
59
- end
60
- end
16
+ class RecordArgProcessor < StructArgProcessor
17
+ include MultiFieldArgSeparator
61
18
 
62
- def define_field_accessors_for(name, index)
63
- define_method(name) do
64
- instantiate_obj_at(index) unless @field_objs[index]
65
- @field_objs[index]
66
- end
67
- define_method(name.to_s + "=") do |*vals|
68
- instantiate_obj_at(index) unless @field_objs[index]
69
- @field_objs[index].assign(*vals)
70
- end
71
- end
19
+ def sanitize_parameters!(obj_class, params)
20
+ super(obj_class, params.merge!(obj_class.dsl_params))
72
21
  end
73
22
  end
74
23
  end
@@ -24,6 +24,14 @@ module BinData
24
24
  end
25
25
  end
26
26
 
27
+ def has_parameter?(param)
28
+ if @factory
29
+ @factory.has_parameter?(param)
30
+ else
31
+ @obj_params.has_parameter?(param)
32
+ end
33
+ end
34
+
27
35
  def instantiate(value = nil, parent = nil)
28
36
  @factory ||= @obj_class.new(@obj_params)
29
37
 
@@ -48,6 +56,10 @@ module BinData
48
56
  @name
49
57
  end
50
58
 
59
+ def has_parameter?(param)
60
+ @prototype.has_parameter?(param)
61
+ end
62
+
51
63
  def instantiate(value = nil, parent = nil)
52
64
  @prototype.instantiate(value, parent)
53
65
  end
@@ -55,6 +67,8 @@ module BinData
55
67
  #----------------------------------------------------------------------------
56
68
 
57
69
  class SanitizedFields < SanitizedParameter
70
+ include Enumerable
71
+
58
72
  def initialize(endian)
59
73
  @fields = []
60
74
  @endian = endian
@@ -83,10 +97,6 @@ module BinData
83
97
  @fields.each(&block)
84
98
  end
85
99
 
86
- def collect(&block)
87
- @fields.collect(&block)
88
- end
89
-
90
100
  def field_names
91
101
  @fields.collect { |field| field.name_as_sym }
92
102
  end
@@ -103,6 +113,10 @@ module BinData
103
113
  @fields.all? { |f| f.name != nil }
104
114
  end
105
115
 
116
+ def any_field_has_parameter?(parameter)
117
+ @fields.any? { |f| f.has_parameter?(parameter) }
118
+ end
119
+
106
120
  def copy_fields(other)
107
121
  @fields.concat(other.fields)
108
122
  end
@@ -207,6 +221,20 @@ module BinData
207
221
  end
208
222
  end
209
223
 
224
+ def must_be_integer(*keys)
225
+ keys.each do |key|
226
+ if has_parameter?(key)
227
+ parameter = self[key]
228
+ unless Symbol === parameter or
229
+ parameter.respond_to? :arity or
230
+ parameter.respond_to? :to_int
231
+ raise ArgumentError, "parameter '#{key}' in #{@the_class} must " +
232
+ "evaluate to an integer, got #{parameter.class}"
233
+ end
234
+ end
235
+ end
236
+ end
237
+
210
238
  def endian
211
239
  @endian || self[:endian]
212
240
  end
@@ -217,6 +245,8 @@ module BinData
217
245
  BIG_ENDIAN
218
246
  elsif endian == :little
219
247
  LITTLE_ENDIAN
248
+ elsif endian == :big_and_little
249
+ raise ArgumentError, ":endian => :big or :endian => :little is required"
220
250
  else
221
251
  raise ArgumentError, "unknown value for endian '#{endian}'"
222
252
  end
@@ -245,7 +275,7 @@ module BinData
245
275
  ensure_no_nil_values
246
276
  merge_default_parameters!
247
277
 
248
- @the_class.sanitize_parameters!(self)
278
+ @the_class.arg_processor.sanitize_parameters!(@the_class, self)
249
279
 
250
280
  ensure_mandatory_parameters_exist
251
281
  ensure_mutual_exclusion_of_parameters