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.
- data/ChangeLog +9 -3
- data/README +10 -0
- data/examples/gzip.rb +32 -45
- data/lib/bindata.rb +2 -1
- data/lib/bindata/array.rb +51 -51
- data/lib/bindata/base.rb +69 -39
- data/lib/bindata/bits.rb +807 -0
- data/lib/bindata/choice.rb +3 -3
- data/lib/bindata/float.rb +2 -2
- data/lib/bindata/int.rb +5 -5
- data/lib/bindata/io.rb +192 -0
- data/lib/bindata/rest.rb +1 -1
- data/lib/bindata/single.rb +42 -53
- data/lib/bindata/string.rb +1 -1
- data/lib/bindata/stringz.rb +1 -1
- data/lib/bindata/struct.rb +45 -37
- data/spec/array_spec.rb +37 -0
- data/spec/base_spec.rb +34 -17
- data/spec/bits_spec.rb +139 -0
- data/spec/io_spec.rb +288 -0
- data/spec/single_spec.rb +4 -3
- data/spec/string_spec.rb +2 -1
- data/spec/struct_spec.rb +25 -0
- metadata +6 -2
data/lib/bindata/choice.rb
CHANGED
@@ -140,9 +140,9 @@ module BinData
|
|
140
140
|
@last_key = nil
|
141
141
|
end
|
142
142
|
|
143
|
-
def_delegators :the_choice, :clear, :clear?, :
|
144
|
-
def_delegators :the_choice, :
|
145
|
-
def_delegators :the_choice, :
|
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(
|
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
|
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(
|
42
|
-
when 16; "readbytes(
|
43
|
-
when 32; "readbytes(
|
44
|
-
when 64; "(a = readbytes(
|
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
|
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
data/lib/bindata/single.rb
CHANGED
@@ -71,38 +71,14 @@ module BinData
|
|
71
71
|
@value.nil?
|
72
72
|
end
|
73
73
|
|
74
|
-
#
|
75
|
-
def
|
76
|
-
|
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
|
-
#
|
104
|
-
def
|
105
|
-
|
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
|
-
#
|
114
|
-
def
|
115
|
-
|
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
|
|
data/lib/bindata/string.rb
CHANGED
@@ -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(
|
110
|
+
io.readbytes(len)
|
111
111
|
end
|
112
112
|
|
113
113
|
# Returns an empty string as default.
|
data/lib/bindata/stringz.rb
CHANGED
data/lib/bindata/struct.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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] }
|