bindata 0.9.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of bindata might be problematic. Click here for more details.

@@ -140,9 +140,9 @@ module BinData
140
140
  @last_key = nil
141
141
  end
142
142
 
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
143
+ def_delegators :the_choice, :clear, :clear?, :single_value?, :field_names
144
+ def_delegators :the_choice, :snapshot, :done_read
145
+ def_delegators :the_choice, :_do_read, :_do_write, :_do_num_bytes
146
146
 
147
147
  # Returns the data object that stores values for +name+.
148
148
  def find_obj_for_name(name)
data/lib/bindata/float.rb CHANGED
@@ -21,7 +21,7 @@ module BinData
21
21
  nbytes = 8
22
22
  end
23
23
 
24
- "readbytes(io,#{nbytes}).unpack('#{unpack}').at(0)"
24
+ "io.readbytes(#{nbytes}).unpack('#{unpack}').at(0)"
25
25
  end
26
26
 
27
27
  def self.create_to_s_code(single_precision, endian)
@@ -39,7 +39,7 @@ module BinData
39
39
 
40
40
  # define methods in the given class
41
41
  klass.module_eval <<-END
42
- def _num_bytes(ignored)
42
+ def _do_num_bytes(ignored)
43
43
  #{nbytes}
44
44
  end
45
45
 
data/lib/bindata/int.rb CHANGED
@@ -38,10 +38,10 @@ module BinData
38
38
  b2 = (endian == :little) ? 1 : 0
39
39
 
40
40
  case nbits
41
- when 8; "readbytes(io,1)[0]"
42
- when 16; "readbytes(io,2).unpack('#{c16}').at(0)"
43
- when 32; "readbytes(io,4).unpack('#{c32}').at(0)"
44
- when 64; "(a = readbytes(io,8).unpack('#{c32 * 2}'); " +
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
45
  "(a.at(#{b2}) << 32) + a.at(#{b1}))"
46
46
  else
47
47
  raise "unknown nbits '#{nbits}'"
@@ -73,7 +73,7 @@ module BinData
73
73
  super(val)
74
74
  end
75
75
 
76
- def _num_bytes(ignored)
76
+ def _do_num_bytes(ignored)
77
77
  #{nbytes}
78
78
  end
79
79
 
data/lib/bindata/io.rb ADDED
@@ -0,0 +1,192 @@
1
+ module BinData
2
+ # A wrapper around an IO object. The wrapper provides a consistent
3
+ # interface for BinData objects to use when accessing the IO.
4
+ class IO
5
+
6
+ # Create a new IO wrapper around +io+. +io+ must support #read if used
7
+ # for reading, #write if used for writing, #pos if reading the current
8
+ # stream position and #seek if setting the current stream position. If
9
+ # +io+ is a string it will be automatically wrapped in an StringIO object.
10
+ #
11
+ # The IO can handle bitstreams in either big or little endian format.
12
+ #
13
+ # M byte1 L M byte2 L
14
+ # S 76543210 S S fedcba98 S
15
+ # B B B B
16
+ #
17
+ # In big endian format:
18
+ # readbits(6), readbits(5) #=> [765432, 10fed]
19
+ #
20
+ # In little endian format:
21
+ # readbits(6), readbits(5) #=> [543210, a9876]
22
+ #
23
+ def initialize(io)
24
+ raise ArgumentError, "io must not be a BinData::IO" if BinData::IO === io
25
+
26
+ # wrap strings in a StringIO
27
+ if io.respond_to?(:to_str)
28
+ io = StringIO.new(io)
29
+ end
30
+
31
+ @raw_io = io
32
+
33
+ # initial stream position if stream supports positioning
34
+ @initial_pos = io.respond_to?(:pos) ? io.pos : 0
35
+
36
+ # bits when reading
37
+ @rnbits = 0
38
+ @rval = 0
39
+ @rendian = nil
40
+
41
+ # bits when writing
42
+ @wnbits = 0
43
+ @wval = 0
44
+ @wendian = nil
45
+ end
46
+
47
+ # Access to the underlying raw io.
48
+ attr_reader :raw_io
49
+
50
+ # Returns the current offset of the io stream. The exact value of
51
+ # the offset when reading bitfields is not defined.
52
+ def offset
53
+ if @raw_io.respond_to?(:pos)
54
+ @raw_io.pos - @initial_pos
55
+ else
56
+ # stream does not support positioning
57
+ 0
58
+ end
59
+ end
60
+
61
+ # Seek +n+ bytes from the current position in the io stream.
62
+ def seekbytes(n)
63
+ @raw_io.seek(n, ::IO::SEEK_CUR)
64
+ end
65
+
66
+ # Reads exactly +n+ bytes from +io+.
67
+ #
68
+ # If the data read is nil an EOFError is raised.
69
+ #
70
+ # If the data read is too short an IOError is raised.
71
+ def readbytes(n)
72
+ raise "Internal state error nbits = #{@rnbits}" if @rnbits > 8
73
+ @rnbits = 0
74
+ @rval = 0
75
+
76
+ str = @raw_io.read(n)
77
+ raise EOFError, "End of file reached" if str.nil?
78
+ raise IOError, "data truncated" if str.size < n
79
+ str
80
+ end
81
+
82
+ # Reads exactly +nbits+ bits from +io+. +endian+ specifies whether
83
+ # the bits are stored in :big or :little endian format.
84
+ def readbits(nbits, endian = :big)
85
+ if @rendian != endian
86
+ # don't mix bits of differing endian
87
+ @rnbits = 0
88
+ @rval = 0
89
+ @rendian = endian
90
+ end
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
+ if endian == :big
107
+ val = (@rval >> (@rnbits - nbits)) & ((1 << nbits) - 1)
108
+ @rnbits -= nbits
109
+ @rval &= ((1 << @rnbits) - 1)
110
+ else
111
+ val = @rval & ((1 << nbits) - 1)
112
+ @rnbits -= nbits
113
+ @rval >>= nbits
114
+ end
115
+
116
+ val
117
+ end
118
+
119
+ # Writes the given string of bytes to the io stream.
120
+ def writebytes(str)
121
+ flushbits
122
+ @raw_io.write(str)
123
+ end
124
+
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.
127
+ def writebits(val, nbits, endian = :big)
128
+ # clamp val to range
129
+ val = val & ((1 << nbits) - 1)
130
+
131
+ if @wendian != endian
132
+ # don't mix bits of differing endian
133
+ flushbits if @wnbits > 0
134
+
135
+ @wendian = endian
136
+ end
137
+
138
+ 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
157
+ 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
176
+ end
177
+ end
178
+
179
+ # To be called after all +writebits+ have been applied.
180
+ def flushbits
181
+ if @wnbits > 8
182
+ raise "Internal state error nbits = #{@wnbits}" if @wnbits > 8
183
+ elsif @wnbits > 0
184
+ writebits(0, 8 - @wnbits, @wendian)
185
+ else
186
+ # do nothing
187
+ end
188
+ end
189
+ alias_method :flush, :flushbits
190
+
191
+ end
192
+ end
data/lib/bindata/rest.rb CHANGED
@@ -30,7 +30,7 @@ module BinData
30
30
 
31
31
  # Read a number of bytes from +io+ and return the value they represent.
32
32
  def read_val(io)
33
- io.read
33
+ io.raw_io.read
34
34
  end
35
35
 
36
36
  # Returns an empty string as default.
@@ -71,38 +71,14 @@ module BinData
71
71
  @value.nil?
72
72
  end
73
73
 
74
- # Reads the value for this data from +io+.
75
- def _do_read(io)
76
- @in_read = true
77
- @value = read_val(io)
78
-
79
- # does the value meet expectations?
80
- if has_param?(:check_value)
81
- current_value = self.value
82
- expected = eval_param(:check_value, :value => current_value)
83
- if not expected
84
- raise ValidityError, "value not as expected"
85
- elsif current_value != expected and expected != true
86
- raise ValidityError, "value is '#{current_value}' but " +
87
- "expected '#{expected}'"
88
- end
89
- end
90
- end
91
-
92
- # To be called after calling #do_read.
93
- def done_read
94
- @in_read = false
95
- end
96
-
97
- # Writes the value for this data to +io+.
98
- def _write(io)
99
- raise "can't write whilst reading" if @in_read
100
- io.write(val_to_str(_value))
74
+ # Single objects are single_values
75
+ def single_value?
76
+ true
101
77
  end
102
78
 
103
- # Returns the number of bytes it will take to write this data.
104
- def _num_bytes(ignored)
105
- val_to_str(_value).length
79
+ # Single objects don't contain fields so this returns an empty list.
80
+ def field_names
81
+ []
106
82
  end
107
83
 
108
84
  # Returns a snapshot of this data object.
@@ -110,14 +86,9 @@ module BinData
110
86
  value
111
87
  end
112
88
 
113
- # Single objects are single_values
114
- def single_value?
115
- true
116
- end
117
-
118
- # Single objects don't contain fields so this returns an empty list.
119
- def field_names
120
- []
89
+ # To be called after calling #do_read.
90
+ def done_read
91
+ @in_read = false
121
92
  end
122
93
 
123
94
  # Returns the current value of this data.
@@ -131,12 +102,45 @@ module BinData
131
102
  unless has_param?(:value)
132
103
  raise ArgumentError, "can't set a nil value" if v.nil?
133
104
  @value = v
105
+
106
+ # Note that this doesn't do anything in ruby 1.8.x so ignore for now
107
+ # # explicitly return the output of #value as v may be different
108
+ # self.value
134
109
  end
135
110
  end
136
111
 
137
112
  #---------------
138
113
  private
139
114
 
115
+ # Reads the value for this data from +io+.
116
+ def _do_read(io)
117
+ @in_read = true
118
+ @value = read_val(io)
119
+
120
+ # does the value meet expectations?
121
+ if has_param?(:check_value)
122
+ current_value = self.value
123
+ expected = eval_param(:check_value, :value => current_value)
124
+ if not expected
125
+ raise ValidityError, "value not as expected"
126
+ elsif current_value != expected and expected != true
127
+ raise ValidityError, "value is '#{current_value}' but " +
128
+ "expected '#{expected}'"
129
+ end
130
+ end
131
+ end
132
+
133
+ # Writes the value for this data to +io+.
134
+ def _do_write(io)
135
+ raise "can't write whilst reading" if @in_read
136
+ io.writebytes(val_to_str(_value))
137
+ end
138
+
139
+ # Returns the number of bytes it will take to write this data.
140
+ def _do_num_bytes(ignored)
141
+ val_to_str(_value).length
142
+ end
143
+
140
144
  # The unmodified value of this data object. Note that #value calls this
141
145
  # method. This is so that #value can be overridden in subclasses to
142
146
  # modify the value.
@@ -159,21 +163,6 @@ module BinData
159
163
  end
160
164
  end
161
165
 
162
- # Usuable by subclasses
163
-
164
- # Reads exactly +n+ bytes from +io+. This should be used by subclasses
165
- # in preference to <tt>io.read(n)</tt>.
166
- #
167
- # If the data read is nil an EOFError is raised.
168
- #
169
- # If the data read is too short an IOError is raised.
170
- def readbytes(io, n)
171
- str = io.read(n)
172
- raise EOFError, "End of file reached" if str == nil
173
- raise IOError, "data truncated" if str.size < n
174
- str
175
- end
176
-
177
166
  ###########################################################################
178
167
  # To be implemented by subclasses
179
168
 
@@ -107,7 +107,7 @@ module BinData
107
107
  # Read a number of bytes from +io+ and return the value they represent.
108
108
  def read_val(io)
109
109
  len = eval_param(:read_length) || eval_param(:length) || 0
110
- readbytes(io, len)
110
+ io.readbytes(len)
111
111
  end
112
112
 
113
113
  # Returns an empty string as default.
@@ -58,7 +58,7 @@ module BinData
58
58
 
59
59
  # read until zero byte or we have read in the max number of bytes
60
60
  while ch != "\0" and i != max_length
61
- ch = readbytes(io, 1)
61
+ ch = io.readbytes(1)
62
62
  str << ch
63
63
  i += 1
64
64
  end
@@ -282,41 +282,6 @@ module BinData
282
282
  end
283
283
  end
284
284
 
285
- # Reads the values for all fields in this object from +io+.
286
- def _do_read(io)
287
- bindata_objects.each { |f| f.do_read(io) }
288
- end
289
-
290
- # To be called after calling #read.
291
- def done_read
292
- bindata_objects.each { |f| f.done_read }
293
- end
294
-
295
- # Writes the values for all fields in this object to +io+.
296
- def _write(io)
297
- bindata_objects.each { |f| f.write(io) }
298
- end
299
-
300
- # Returns the number of bytes it will take to write the field represented
301
- # by +name+. If +name+ is nil then returns the number of bytes required
302
- # to write all fields.
303
- def _num_bytes(name)
304
- if name.nil?
305
- bindata_objects.inject(0) { |sum, f| sum + f.num_bytes }
306
- else
307
- find_obj_for_name(name.to_s).num_bytes
308
- end
309
- end
310
-
311
- # Returns a snapshot of this struct as a hash.
312
- def snapshot
313
- hash = Snapshot.new
314
- field_names.each do |name|
315
- hash[name] = find_obj_for_name(name).snapshot
316
- end
317
- hash
318
- end
319
-
320
285
  # Returns whether this data object contains a single value. Single
321
286
  # value data objects respond to <tt>#value</tt> and <tt>#value=</tt>.
322
287
  def single_value?
@@ -342,6 +307,20 @@ module BinData
342
307
  names
343
308
  end
344
309
 
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
+ # To be called after calling #read.
320
+ def done_read
321
+ bindata_objects.each { |f| f.done_read }
322
+ end
323
+
345
324
  # Returns the data object that stores values for +name+.
346
325
  def find_obj_for_name(name)
347
326
  @fields.each do |n, o|
@@ -360,9 +339,17 @@ module BinData
360
339
  @fields.each do |name, obj|
361
340
  if name != ""
362
341
  break if name == field_name
363
- offset += obj.num_bytes
342
+ this_offset = obj.do_num_bytes
343
+ if ::Float === offset and ::Integer === this_offset
344
+ offset = offset.ceil
345
+ end
346
+ offset += this_offset
364
347
  elsif obj.field_names.include?(field_name)
365
- offset += obj.offset_of(field)
348
+ this_offset = obj.offset_of(field)
349
+ if ::Float === offset and ::Integer === this_offset
350
+ offset = offset.ceil
351
+ end
352
+ offset += this_offset
366
353
  break
367
354
  end
368
355
  end
@@ -400,6 +387,27 @@ module BinData
400
387
  #---------------
401
388
  private
402
389
 
390
+ # Reads the values for all fields in this object from +io+.
391
+ def _do_read(io)
392
+ bindata_objects.each { |f| f.do_read(io) }
393
+ end
394
+
395
+ # Writes the values for all fields in this object to +io+.
396
+ def _do_write(io)
397
+ bindata_objects.each { |f| f.do_write(io) }
398
+ end
399
+
400
+ # Returns the number of bytes it will take to write the field represented
401
+ # by +name+. If +name+ is nil then returns the number of bytes required
402
+ # to write all fields.
403
+ def _do_num_bytes(name)
404
+ if name.nil?
405
+ (bindata_objects.inject(0) { |sum, f| sum + f.do_num_bytes }).ceil
406
+ else
407
+ find_obj_for_name(name.to_s).do_num_bytes
408
+ end
409
+ end
410
+
403
411
  # Returns a list of all the bindata objects for this struct.
404
412
  def bindata_objects
405
413
  @fields.collect { |f| f[1] }