bindata 0.9.2 → 0.9.3

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.

@@ -4,13 +4,18 @@ require 'bindata/sanitize'
4
4
 
5
5
  module BinData
6
6
  # A Choice is a collection of data objects of which only one is active
7
- # at any particular time.
7
+ # at any particular time. Method calls will be delegated to the active
8
+ # choice.
8
9
  #
9
10
  # require 'bindata'
10
11
  #
11
12
  # type1 = [:string, {:value => "Type1"}]
12
13
  # type2 = [:string, {:value => "Type2"}]
13
14
  #
15
+ # choices = {5 => type1, 17 => type2}
16
+ # a = BinData::Choice.new(:choices => choices, :selection => 5)
17
+ # a.value # => "Type1"
18
+ #
14
19
  # choices = [ type1, type2 ]
15
20
  # a = BinData::Choice.new(:choices => choices, :selection => 1)
16
21
  # a.value # => "Type2"
@@ -19,17 +24,14 @@ module BinData
19
24
  # a = BinData::Choice.new(:choices => choices, :selection => 3)
20
25
  # a.value # => "Type1"
21
26
  #
22
- # choices = {5 => type1, 17 => type2}
23
- # a = BinData::Choice.new(:choices => choices, :selection => 5)
24
- # a.value # => "Type1"
25
- #
26
27
  # mychoice = 'big'
27
28
  # choices = {'big' => :uint16be, 'little' => :uint16le}
28
29
  # a = BinData::Choice.new(:choices => choices,
29
30
  # :selection => lambda { mychoice })
30
31
  # a.value = 256
31
32
  # a.to_s #=> "\001\000"
32
- # mychoice[0..-1] = 'little'
33
+ # mychoice.replace 'little'
34
+ # a.selection #=> 'little'
33
35
  # a.to_s #=> "\000\001"
34
36
  #
35
37
  #
@@ -54,62 +56,91 @@ module BinData
54
56
  register(self.name, self)
55
57
 
56
58
  # These are the parameters used by this class.
57
- mandatory_parameters :choices, :selection
59
+ bindata_mandatory_parameters :choices, :selection
58
60
 
59
61
  class << self
60
62
 
61
- # Returns a sanitized +params+ that is of the form expected
62
- # by #initialize.
63
- def sanitize_parameters(sanitizer, params)
64
- params = params.dup
65
-
63
+ # Ensures that +params+ is of the form expected by #initialize.
64
+ def sanitize_parameters!(sanitizer, params)
66
65
  if params.has_key?(:choices)
67
66
  choices = params[:choices]
68
67
 
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
- new_choices[key] = sanitizer.sanitize(type, param)
68
+ # convert array to hash keyed by index
69
+ if ::Array === choices
70
+ tmp = {}
71
+ choices.each_with_index do |el, i|
72
+ tmp[i] = el unless el.nil?
84
73
  end
85
- params[:choices] = new_choices
86
- when ::Array
87
- choices.collect! do |type, param|
88
- if type.nil?
89
- # allow sparse arrays
90
- nil
91
- else
92
- sanitizer.sanitize(type, param)
93
- end
94
- end
95
- params[:choices] = choices
96
- else
97
- raise ArgumentError, "unknown type for :choices (#{choices.class})"
74
+ choices = tmp
98
75
  end
76
+
77
+ # ensure valid hash keys
78
+ if choices.has_key?(nil)
79
+ raise ArgumentError, ":choices hash may not have nil key"
80
+ end
81
+ if choices.keys.detect { |k| Symbol === k }
82
+ raise ArgumentError, ":choices hash may not have symbols for keys"
83
+ end
84
+
85
+ # sanitize each choice
86
+ new_choices = {}
87
+ choices.each_pair do |key, val|
88
+ type, param = val
89
+ klass = sanitizer.lookup_klass(type)
90
+ sanitized_params = sanitizer.sanitize_params(klass, param)
91
+ new_choices[key] = [klass, sanitized_params]
92
+ end
93
+ params[:choices] = new_choices
99
94
  end
100
95
 
101
96
  super(sanitizer, params)
102
97
  end
103
98
  end
104
99
 
105
- def initialize(params = {}, env = nil)
106
- super(params, env)
100
+ def initialize(params = {}, parent = nil)
101
+ super(params, parent)
107
102
 
108
- # prepare collection of instantiated choice objects
109
- @choices = (param(:choices) === ::Array) ? [] : {}
103
+ @choices = {}
110
104
  @last_key = nil
111
105
  end
112
106
 
107
+ # A convenience method that returns the current selection.
108
+ def selection
109
+ eval_param(:selection)
110
+ end
111
+
112
+ # This method does not exist. This stub only exists to document why.
113
+ # There is no #selection= method to complement the #selection method.
114
+ # This is deliberate to promote the declarative nature of BinData.
115
+ #
116
+ # If you really *must* be able to programmatically adjust the selection
117
+ # then try something like the following.
118
+ #
119
+ # class ProgrammaticChoice < BinData::MultiValue
120
+ # choice :data, :choices => :choices, :selection => :selection
121
+ # attrib_accessor :selection
122
+ # end
123
+ #
124
+ # type1 = [:string, {:value => "Type1"}]
125
+ # type2 = [:string, {:value => "Type2"}]
126
+ #
127
+ # choices = {5 => type1, 17 => type2}
128
+ # pc = ProgrammaticChoice.new(:choices => choices)
129
+ #
130
+ # pc.selection = 5
131
+ # pc.data #=> "Type1"
132
+ #
133
+ # pc.selection = 17
134
+ # pc.data #=> "Type2"
135
+ def selection=(v)
136
+ raise NoMethodError
137
+ end
138
+
139
+ # A choice represents a specific object.
140
+ def obj
141
+ the_choice
142
+ end
143
+
113
144
  def_delegators :the_choice, :clear, :clear?, :single_value?
114
145
  def_delegators :the_choice, :done_read, :_snapshot
115
146
  def_delegators :the_choice, :_do_read, :_do_write, :_do_num_bytes
@@ -141,11 +172,11 @@ module BinData
141
172
  obj = @choices[key]
142
173
  if obj.nil?
143
174
  # instantiate choice object
144
- choice_klass, choice_params = param(:choices)[key]
175
+ choice_klass, choice_params = no_eval_param(:choices)[key]
145
176
  if choice_klass.nil?
146
177
  raise IndexError, "selection #{key} does not exist in :choices"
147
178
  end
148
- obj = choice_klass.new(choice_params, create_env)
179
+ obj = choice_klass.new(choice_params, self)
149
180
  @choices[key] = obj
150
181
  end
151
182
 
data/lib/bindata/int.rb CHANGED
@@ -34,15 +34,21 @@ module BinData
34
34
  def self.create_read_code(nbits, endian)
35
35
  c16 = (endian == :little) ? 'v' : 'n'
36
36
  c32 = (endian == :little) ? 'V' : 'N'
37
- b1 = (endian == :little) ? 0 : 1
38
- b2 = (endian == :little) ? 1 : 0
37
+ idx = (0 ... (nbits / 32)).to_a
38
+ idx.reverse! if (endian == :little)
39
39
 
40
40
  case nbits
41
- when 8; "io.readbytes(1).unpack('C').at(0)"
42
- when 16; "io.readbytes(2).unpack('#{c16}').at(0)"
43
- when 32; "io.readbytes(4).unpack('#{c32}').at(0)"
44
- when 64; "(a = io.readbytes(8).unpack('#{c32 * 2}'); " +
45
- "(a.at(#{b2}) << 32) + a.at(#{b1}))"
41
+ when 8; "io.readbytes(1).unpack('C').at(0)"
42
+ when 16; "io.readbytes(2).unpack('#{c16}').at(0)"
43
+ when 32; "io.readbytes(4).unpack('#{c32}').at(0)"
44
+ when 64; "(a = io.readbytes(8).unpack('#{c32 * 2}'); " +
45
+ "(a.at(#{idx[0]}) << 32) + " +
46
+ "a.at(#{idx[1]}))"
47
+ when 128; "(a = io.readbytes(16).unpack('#{c32 * 4}'); " +
48
+ "((a.at(#{idx[0]}) << 96) + " +
49
+ "(a.at(#{idx[1]}) << 64) + " +
50
+ "(a.at(#{idx[2]}) << 32) + " +
51
+ "a.at(#{idx[3]})))"
46
52
  else
47
53
  raise "unknown nbits '#{nbits}'"
48
54
  end
@@ -51,14 +57,19 @@ module BinData
51
57
  def self.create_to_s_code(nbits, endian)
52
58
  c16 = (endian == :little) ? 'v' : 'n'
53
59
  c32 = (endian == :little) ? 'V' : 'N'
54
- v1 = (endian == :little) ? 'val' : '(val >> 32)'
55
- v2 = (endian == :little) ? '(val >> 32)' : 'val'
60
+ vals = (0 ... (nbits / 32)).collect { |i| "(val >> #{32 * i})" }
61
+ vals.reverse! if (endian == :little)
56
62
 
57
63
  case nbits
58
- when 8; "val.chr"
59
- when 16; "[val].pack('#{c16}')"
60
- when 32; "[val].pack('#{c32}')"
61
- when 64; "[#{v1} & 0xffffffff, #{v2} & 0xffffffff].pack('#{c32 * 2}')"
64
+ when 8; "val.chr"
65
+ when 16; "[val].pack('#{c16}')"
66
+ when 32; "[val].pack('#{c32}')"
67
+ when 64; "[#{vals[1]} & 0xffffffff, " +
68
+ "#{vals[0]} & 0xffffffff].pack('#{c32 * 2}')"
69
+ when 128; "[#{vals[3]} & 0xffffffff, " +
70
+ "#{vals[2]} & 0xffffffff, " +
71
+ "#{vals[1]} & 0xffffffff, " +
72
+ "#{vals[0]} & 0xffffffff].pack('#{c32 * 4}')"
62
73
  else
63
74
  raise "unknown nbits '#{nbits}'"
64
75
  end
@@ -141,6 +152,18 @@ module BinData
141
152
  Integer.create_uint_methods(self, 64, :big)
142
153
  end
143
154
 
155
+ # Unsigned 16 byte little endian integer.
156
+ class Uint128le < BinData::Single
157
+ register(self.name, self)
158
+ Integer.create_uint_methods(self, 128, :little)
159
+ end
160
+
161
+ # Unsigned 16 byte big endian integer.
162
+ class Uint128be < BinData::Single
163
+ register(self.name, self)
164
+ Integer.create_uint_methods(self, 128, :big)
165
+ end
166
+
144
167
  # Signed 1 byte integer.
145
168
  class Int8 < BinData::Single
146
169
  register(self.name, self)
@@ -182,4 +205,16 @@ module BinData
182
205
  register(self.name, self)
183
206
  Integer.create_int_methods(self, 64, :big)
184
207
  end
208
+
209
+ # Signed 16 byte little endian integer.
210
+ class Int128le < BinData::Single
211
+ register(self.name, self)
212
+ Integer.create_int_methods(self, 128, :little)
213
+ end
214
+
215
+ # Signed 16 byte big endian integer.
216
+ class Int128be < BinData::Single
217
+ register(self.name, self)
218
+ Integer.create_int_methods(self, 128, :big)
219
+ end
185
220
  end
data/lib/bindata/io.rb CHANGED
@@ -31,7 +31,7 @@ module BinData
31
31
  @raw_io = io
32
32
 
33
33
  # initial stream position if stream supports positioning
34
- @initial_pos = io.respond_to?(:pos) ? io.pos : 0
34
+ @initial_pos = positioning_supported? ? io.pos : 0
35
35
 
36
36
  # bits when reading
37
37
  @rnbits = 0
@@ -50,7 +50,7 @@ module BinData
50
50
  # Returns the current offset of the io stream. The exact value of
51
51
  # the offset when reading bitfields is not defined.
52
52
  def offset
53
- if @raw_io.respond_to?(:pos)
53
+ if positioning_supported?
54
54
  @raw_io.pos - @initial_pos
55
55
  else
56
56
  # stream does not support positioning
@@ -79,8 +79,8 @@ module BinData
79
79
  str
80
80
  end
81
81
 
82
- # Reads exactly +nbits+ bits from +io+. +endian+ specifies whether
83
- # the bits are stored in :big or :little endian format.
82
+ # Reads exactly +nbits+ bits from the stream. +endian+ specifies whether
83
+ # the bits are stored in +:big+ or +:little+ endian format.
84
84
  def readbits(nbits, endian = :big)
85
85
  if @rendian != endian
86
86
  # don't mix bits of differing endian
@@ -89,31 +89,11 @@ module BinData
89
89
  @rendian = endian
90
90
  end
91
91
 
92
- while nbits > @rnbits
93
- byte = @raw_io.read(1)
94
- raise EOFError, "End of file reached" if byte.nil?
95
- byte = byte.unpack('C').at(0) & 0xff
96
-
97
- if endian == :big
98
- @rval = (@rval << 8) | byte
99
- else
100
- @rval = @rval | (byte << @rnbits)
101
- end
102
-
103
- @rnbits += 8
104
- end
105
-
106
92
  if endian == :big
107
- val = (@rval >> (@rnbits - nbits)) & ((1 << nbits) - 1)
108
- @rnbits -= nbits
109
- @rval &= ((1 << @rnbits) - 1)
93
+ read_be_bits(nbits)
110
94
  else
111
- val = @rval & ((1 << nbits) - 1)
112
- @rnbits -= nbits
113
- @rval >>= nbits
95
+ read_le_bits(nbits)
114
96
  end
115
-
116
- val
117
97
  end
118
98
 
119
99
  # Writes the given string of bytes to the io stream.
@@ -122,8 +102,8 @@ module BinData
122
102
  @raw_io.write(str)
123
103
  end
124
104
 
125
- # Reads +nbits+ bits from +val+ to the stream. +endian+ specifies whether
126
- # the bits are to be stored in :big or :little endian format.
105
+ # Writes +nbits+ bits from +val+ to the stream. +endian+ specifies whether
106
+ # the bits are to be stored in +:big+ or +:little+ endian format.
127
107
  def writebits(val, nbits, endian = :big)
128
108
  # clamp val to range
129
109
  val = val & ((1 << nbits) - 1)
@@ -136,43 +116,9 @@ module BinData
136
116
  end
137
117
 
138
118
  if endian == :big
139
- while nbits > 0
140
- bits_req = 8 - @wnbits
141
- if nbits >= bits_req
142
- msb_bits = (val >> (nbits - bits_req)) & ((1 << bits_req) - 1)
143
- nbits -= bits_req
144
- val &= (1 << nbits) - 1
145
-
146
- @wval = (@wval << bits_req) | msb_bits
147
- @raw_io.write(@wval.chr)
148
-
149
- @wval = 0
150
- @wnbits = 0
151
- else
152
- @wval = (@wval << nbits) | val
153
- @wnbits += nbits
154
- nbits = 0
155
- end
156
- end
119
+ write_be_bits(val, nbits)
157
120
  else
158
- while nbits > 0
159
- bits_req = 8 - @wnbits
160
- if nbits >= bits_req
161
- lsb_bits = val & ((1 << bits_req) - 1)
162
- nbits -= bits_req
163
- val >>= bits_req
164
-
165
- @wval |= (lsb_bits << @wnbits)
166
- @raw_io.write(@wval.chr)
167
-
168
- @wval = 0
169
- @wnbits = 0
170
- else
171
- @wval |= (val << @wnbits)
172
- @wnbits += nbits
173
- nbits = 0
174
- end
175
- end
121
+ write_le_bits(val, nbits)
176
122
  end
177
123
  end
178
124
 
@@ -188,5 +134,102 @@ module BinData
188
134
  end
189
135
  alias_method :flush, :flushbits
190
136
 
137
+ #---------------
138
+ private
139
+
140
+ # Checks if positioning is supported by the underlying io stream.
141
+ def positioning_supported?
142
+ unless defined? @positioning_supported
143
+ @positioning_supported = begin
144
+ @raw_io.pos
145
+ true
146
+ rescue NoMethodError, Errno::ESPIPE
147
+ false
148
+ end
149
+ end
150
+ @positioning_supported
151
+ end
152
+
153
+ # Reads exactly +nbits+ big endian bits from the stream.
154
+ def read_be_bits(nbits)
155
+ # ensure enough bits have accumulated
156
+ while nbits > @rnbits
157
+ byte = @raw_io.read(1)
158
+ raise EOFError, "End of file reached" if byte.nil?
159
+ byte = byte.unpack('C').at(0) & 0xff
160
+
161
+ @rval = (@rval << 8) | byte
162
+ @rnbits += 8
163
+ end
164
+
165
+ val = (@rval >> (@rnbits - nbits)) & ((1 << nbits) - 1)
166
+ @rnbits -= nbits
167
+ @rval &= ((1 << @rnbits) - 1)
168
+
169
+ val
170
+ end
171
+
172
+ # Reads exactly +nbits+ little endian bits from the stream.
173
+ def read_le_bits(nbits)
174
+ # ensure enough bits have accumulated
175
+ while nbits > @rnbits
176
+ byte = @raw_io.read(1)
177
+ raise EOFError, "End of file reached" if byte.nil?
178
+ byte = byte.unpack('C').at(0) & 0xff
179
+
180
+ @rval = @rval | (byte << @rnbits)
181
+ @rnbits += 8
182
+ end
183
+
184
+ val = @rval & ((1 << nbits) - 1)
185
+ @rnbits -= nbits
186
+ @rval >>= nbits
187
+
188
+ val
189
+ end
190
+
191
+ # Writes +nbits+ bits from +val+ to the stream in big endian format.
192
+ def write_be_bits(val, nbits)
193
+ while nbits > 0
194
+ bits_req = 8 - @wnbits
195
+ if nbits >= bits_req
196
+ msb_bits = (val >> (nbits - bits_req)) & ((1 << bits_req) - 1)
197
+ nbits -= bits_req
198
+ val &= (1 << nbits) - 1
199
+
200
+ @wval = (@wval << bits_req) | msb_bits
201
+ @raw_io.write(@wval.chr)
202
+
203
+ @wval = 0
204
+ @wnbits = 0
205
+ else
206
+ @wval = (@wval << nbits) | val
207
+ @wnbits += nbits
208
+ nbits = 0
209
+ end
210
+ end
211
+ end
212
+
213
+ # Writes +nbits+ bits from +val+ to the stream in little endian format.
214
+ def write_le_bits(val, nbits)
215
+ while nbits > 0
216
+ bits_req = 8 - @wnbits
217
+ if nbits >= bits_req
218
+ lsb_bits = val & ((1 << bits_req) - 1)
219
+ nbits -= bits_req
220
+ val >>= bits_req
221
+
222
+ @wval |= (lsb_bits << @wnbits)
223
+ @raw_io.write(@wval.chr)
224
+
225
+ @wval = 0
226
+ @wnbits = 0
227
+ else
228
+ @wval |= (val << @wnbits)
229
+ @wnbits += nbits
230
+ nbits = 0
231
+ end
232
+ end
233
+ end
191
234
  end
192
235
  end