bindata 0.9.0 → 0.9.1
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.
- 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/ChangeLog
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
= BinData Changelog
|
2
2
|
|
3
|
+
== Version 0.9.1 (2008-06-15)
|
4
|
+
|
5
|
+
* Implemented bit fields.
|
6
|
+
* Added :onlyif parameter to Base for specifying optional fields.
|
7
|
+
* Fixed IO offset bug with SingleValues.
|
8
|
+
|
3
9
|
== Version 0.9.0 (2008-06-02)
|
4
10
|
|
5
11
|
* Added :adjust_offset option to automatically seek to a given offset.
|
@@ -7,14 +13,14 @@
|
|
7
13
|
* Choice now accepts sparse arrays and hashes as :choice.
|
8
14
|
* Added BinData::Rest to help with debugging.
|
9
15
|
* Major internal restructuring - memory usage is much better.
|
10
|
-
* Improved documentation
|
16
|
+
* Improved documentation.
|
11
17
|
|
12
18
|
== Version 0.8.1 (2008-01-14)
|
13
19
|
|
14
20
|
* Reduced memory consumption.
|
15
21
|
* Increased execution speed.
|
16
|
-
* Deprecated BinData::Base.parameters
|
17
|
-
* Fixed spec syntax (thanks to David Goodlad)
|
22
|
+
* Deprecated BinData::Base.parameters.
|
23
|
+
* Fixed spec syntax (thanks to David Goodlad).
|
18
24
|
|
19
25
|
== Version 0.8.0 (2007-10-14)
|
20
26
|
|
data/README
CHANGED
@@ -175,6 +175,16 @@ BinData::Uint32be:: Unsigned 32 bit integer (big endian).
|
|
175
175
|
BinData::Uint64le:: Unsigned 64 bit integer (little endian).
|
176
176
|
BinData::Uint64be:: Unsigned 64 bit integer (big endian).
|
177
177
|
|
178
|
+
BinData::Bit1:: 1 bit unsigned integer (big endian).
|
179
|
+
BinData::Bit2:: 2 bit unsigned integer (big endian).
|
180
|
+
...
|
181
|
+
BinData::Bit63:: 63 bit unsigned integer (big endian).
|
182
|
+
|
183
|
+
BinData::Bit1le:: 1 bit unsigned integer (little endian).
|
184
|
+
BinData::Bit2le:: 2 bit unsigned integer (little endian).
|
185
|
+
...
|
186
|
+
BinData::Bit63le:: 63 bit unsigned integer (little endian).
|
187
|
+
|
178
188
|
BinData::FloatLe:: Single precision floating point number (little endian).
|
179
189
|
BinData::FloatBe:: Single precision floating point number (big endian).
|
180
190
|
BinData::DoubleLe:: Double precision floating point number (little endian).
|
data/examples/gzip.rb
CHANGED
@@ -10,59 +10,46 @@ class Gzip
|
|
10
10
|
DEFLATE = 8
|
11
11
|
|
12
12
|
class Extra < BinData::MultiValue
|
13
|
-
|
14
|
-
|
13
|
+
endian :little
|
14
|
+
|
15
|
+
uint16 :len, :length => lambda { data.length }
|
16
|
+
string :data, :read_length => :len
|
15
17
|
end
|
16
18
|
|
17
19
|
class Header < BinData::MultiValue
|
18
|
-
|
19
|
-
uint8 :compression_method, :initial_value => DEFLATE
|
20
|
-
uint8 :flags, :value => :calculate_flags_val,
|
21
|
-
# Upper 3 bits must be zero
|
22
|
-
:check_value => lambda { (value & 0xe0) == 0 }
|
23
|
-
uint32le :mtime
|
24
|
-
uint8 :extra_flags
|
25
|
-
uint8 :os, :initial_value => 255 # unknown OS
|
26
|
-
|
27
|
-
# These fields are optional depending on the bits in flags
|
28
|
-
extra :extra, :readwrite => :extra?
|
29
|
-
stringz :file_name, :readwrite => :file_name?
|
30
|
-
stringz :comment, :readwrite => :comment?
|
31
|
-
uint16le :crc16, :readwrite => :crc16?
|
32
|
-
|
33
|
-
|
34
|
-
## Convenience methods for accessing and manipulating flags
|
20
|
+
endian :little
|
35
21
|
|
36
|
-
|
22
|
+
uint16 :ident, :value => 0x8b1f, :check_value => 0x8b1f
|
23
|
+
uint8 :compression_method, :initial_value => DEFLATE
|
37
24
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
25
|
+
bit3 :freserved, :value => 0, :check_value => 0
|
26
|
+
bit1 :fcomment, :value => lambda { comment.length > 0 ? 1 : 0 }
|
27
|
+
bit1 :ffile_name, :value => lambda { file_name.length > 0 ? 1 : 0 }
|
28
|
+
bit1 :fextra, :value => lambda { extra.len > 0 ? 1 : 0 }
|
29
|
+
bit1 :fcrc16, :value => 0 # see comment below
|
30
|
+
bit1 :ftext
|
44
31
|
|
45
|
-
|
32
|
+
# Never include header crc. This is because the current versions of the
|
33
|
+
# command-line version of gzip (up through version 1.3.x) do not
|
34
|
+
# support header crc's, and will report that it is a "multi-part gzip
|
35
|
+
# file" and give up.
|
46
36
|
|
47
|
-
|
48
|
-
|
49
|
-
|
37
|
+
uint32 :mtime
|
38
|
+
uint8 :extra_flags
|
39
|
+
uint8 :os, :initial_value => 255 # unknown OS
|
50
40
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
((!clear?(:extra) ? 1 : 0) << 2) |
|
58
|
-
((!clear?(:file_name) ? 1 : 0) << 3) |
|
59
|
-
((!clear?(:comment) ? 1 : 0) << 4)
|
60
|
-
end
|
41
|
+
# These fields are optional depending on the bits in flags
|
42
|
+
extra :extra, :onlyif => lambda { fextra.nonzero? }
|
43
|
+
stringz :file_name, :onlyif => lambda { ffile_name.nonzero? }
|
44
|
+
stringz :comment, :onlyif => lambda { fcomment.nonzero? }
|
45
|
+
uint16 :crc16, :onlyif => lambda { fcrc16.nonzero? }
|
61
46
|
end
|
62
47
|
|
63
48
|
class Footer < BinData::MultiValue
|
64
|
-
|
65
|
-
|
49
|
+
endian :little
|
50
|
+
|
51
|
+
uint32 :crc32
|
52
|
+
uint32 :uncompressed_size
|
66
53
|
end
|
67
54
|
|
68
55
|
def initialize
|
@@ -71,8 +58,8 @@ class Gzip
|
|
71
58
|
end
|
72
59
|
|
73
60
|
attr_accessor :compressed
|
74
|
-
def_delegators :@header, :file_name=, :file_name
|
75
|
-
def_delegators :@header, :comment=, :comment
|
61
|
+
def_delegators :@header, :file_name=, :file_name
|
62
|
+
def_delegators :@header, :comment=, :comment
|
76
63
|
def_delegators :@header, :compression_method
|
77
64
|
def_delegators :@footer, :crc32, :uncompressed_size
|
78
65
|
|
@@ -166,7 +153,7 @@ if __FILE__ == $0
|
|
166
153
|
g.uncompressed_size,
|
167
154
|
ratio,
|
168
155
|
g.file_name]
|
169
|
-
puts "Comment: #{g.comment}" if g.comment
|
156
|
+
puts "Comment: #{g.comment}" if g.comment != ""
|
170
157
|
puts
|
171
158
|
|
172
159
|
puts "Executing gzip -l -v"
|
data/lib/bindata.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
# Copyright (c) 2007,2008 Dion Mendel.
|
3
3
|
|
4
4
|
require 'bindata/array'
|
5
|
+
require 'bindata/bits'
|
5
6
|
require 'bindata/choice'
|
6
7
|
require 'bindata/float'
|
7
8
|
require 'bindata/int'
|
@@ -17,5 +18,5 @@ require 'bindata/struct'
|
|
17
18
|
# A declarative way to read and write structured binary data.
|
18
19
|
#
|
19
20
|
module BinData
|
20
|
-
VERSION = "0.9.
|
21
|
+
VERSION = "0.9.1"
|
21
22
|
end
|
data/lib/bindata/array.rb
CHANGED
@@ -95,19 +95,6 @@ module BinData
|
|
95
95
|
@element_params = el_params
|
96
96
|
end
|
97
97
|
|
98
|
-
# Clears the element at position +index+. If +index+ is not given, then
|
99
|
-
# the internal state of the array is reset to that of a newly created
|
100
|
-
# object.
|
101
|
-
def clear(index = nil)
|
102
|
-
if @element_list.nil?
|
103
|
-
# do nothing as the array is already clear
|
104
|
-
elsif index.nil?
|
105
|
-
@element_list = nil
|
106
|
-
else
|
107
|
-
elements[index].clear
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
98
|
# Returns if the element at position +index+ is clear?. If +index+
|
112
99
|
# is not given, then returns whether all fields are clear.
|
113
100
|
def clear?(index = nil)
|
@@ -121,49 +108,19 @@ module BinData
|
|
121
108
|
end
|
122
109
|
end
|
123
110
|
|
124
|
-
#
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
111
|
+
# Clears the element at position +index+. If +index+ is not given, then
|
112
|
+
# the internal state of the array is reset to that of a newly created
|
113
|
+
# object.
|
114
|
+
def clear(index = nil)
|
115
|
+
if @element_list.nil?
|
116
|
+
# do nothing as the array is already clear
|
117
|
+
elsif index.nil?
|
129
118
|
@element_list = nil
|
130
|
-
loop do
|
131
|
-
element = append_new_element
|
132
|
-
element.do_read(io)
|
133
|
-
variables = { :index => self.length - 1, :element => self.last,
|
134
|
-
:array => self }
|
135
|
-
finished = eval_param(:read_until, variables)
|
136
|
-
break if finished
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
# To be called after calling #do_read.
|
142
|
-
def done_read
|
143
|
-
elements.each { |f| f.done_read }
|
144
|
-
end
|
145
|
-
|
146
|
-
# Writes the values for all fields in this object to +io+.
|
147
|
-
def _write(io)
|
148
|
-
elements.each { |f| f.write(io) }
|
149
|
-
end
|
150
|
-
|
151
|
-
# Returns the number of bytes it will take to write the element at
|
152
|
-
# +index+. If +index+, then returns the number of bytes required
|
153
|
-
# to write all fields.
|
154
|
-
def _num_bytes(index)
|
155
|
-
if index.nil?
|
156
|
-
elements.inject(0) { |sum, f| sum + f.num_bytes }
|
157
119
|
else
|
158
|
-
elements[index].
|
120
|
+
elements[index].clear
|
159
121
|
end
|
160
122
|
end
|
161
123
|
|
162
|
-
# Returns a snapshot of the data in this array.
|
163
|
-
def snapshot
|
164
|
-
elements.collect { |e| e.snapshot }
|
165
|
-
end
|
166
|
-
|
167
124
|
# Returns whether this data object contains a single value. Single
|
168
125
|
# value data objects respond to <tt>#value</tt> and <tt>#value=</tt>.
|
169
126
|
def single_value?
|
@@ -175,6 +132,16 @@ module BinData
|
|
175
132
|
[]
|
176
133
|
end
|
177
134
|
|
135
|
+
# Returns a snapshot of the data in this array.
|
136
|
+
def snapshot
|
137
|
+
elements.collect { |e| e.snapshot }
|
138
|
+
end
|
139
|
+
|
140
|
+
# To be called after calling #do_read.
|
141
|
+
def done_read
|
142
|
+
elements.each { |f| f.done_read }
|
143
|
+
end
|
144
|
+
|
178
145
|
# Appends a new element to the end of the array. If the array contains
|
179
146
|
# single_values then the +value+ may be provided to the call.
|
180
147
|
# Returns the appended object, or value in the case of single_values.
|
@@ -256,6 +223,39 @@ module BinData
|
|
256
223
|
#---------------
|
257
224
|
private
|
258
225
|
|
226
|
+
# Reads the values for all fields in this object from +io+.
|
227
|
+
def _do_read(io)
|
228
|
+
if has_param?(:initial_length)
|
229
|
+
elements.each { |f| f.do_read(io) }
|
230
|
+
else # :read_until
|
231
|
+
@element_list = nil
|
232
|
+
loop do
|
233
|
+
element = append_new_element
|
234
|
+
element.do_read(io)
|
235
|
+
variables = { :index => self.length - 1, :element => self.last,
|
236
|
+
:array => self }
|
237
|
+
finished = eval_param(:read_until, variables)
|
238
|
+
break if finished
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
# Writes the values for all fields in this object to +io+.
|
244
|
+
def _do_write(io)
|
245
|
+
elements.each { |f| f.do_write(io) }
|
246
|
+
end
|
247
|
+
|
248
|
+
# Returns the number of bytes it will take to write the element at
|
249
|
+
# +index+. If +index+, then returns the number of bytes required
|
250
|
+
# to write all fields.
|
251
|
+
def _do_num_bytes(index)
|
252
|
+
if index.nil?
|
253
|
+
(elements.inject(0) { |sum, f| sum + f.do_num_bytes }).ceil
|
254
|
+
else
|
255
|
+
elements[index].do_num_bytes
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
259
|
# Returns the list of all elements in the array. The elements
|
260
260
|
# will be instantiated on the first call to this method.
|
261
261
|
def elements
|
data/lib/bindata/base.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'bindata/io'
|
1
2
|
require 'bindata/lazy'
|
2
3
|
require 'bindata/sanitize'
|
3
4
|
require 'bindata/registry'
|
@@ -16,6 +17,9 @@ module BinData
|
|
16
17
|
#
|
17
18
|
# [<tt>:readwrite</tt>] If false, calls to #read or #write will
|
18
19
|
# not perform any I/O. Default is true.
|
20
|
+
# [<tt>:onlyif</tt>] This is an alias for :readwrite. It is generally
|
21
|
+
# used to indicate a data object is optional.
|
22
|
+
# not perform any I/O. Default is true.
|
19
23
|
# [<tt>:check_offset</tt>] Raise an error if the current IO offset doesn't
|
20
24
|
# meet this criteria. A boolean return indicates
|
21
25
|
# success or failure. Any other return is compared
|
@@ -116,6 +120,11 @@ module BinData
|
|
116
120
|
def sanitize_parameters(params, *args)
|
117
121
|
params = params.dup
|
118
122
|
|
123
|
+
# replace :onlyif with :readwrite
|
124
|
+
if params.has_key?(:onlyif)
|
125
|
+
params[:readwrite] = params.delete(:onlyif)
|
126
|
+
end
|
127
|
+
|
119
128
|
# add default parameters
|
120
129
|
default_parameters.each do |k,v|
|
121
130
|
params[k] = v unless params.has_key?(k)
|
@@ -198,16 +207,7 @@ module BinData
|
|
198
207
|
|
199
208
|
# Reads data into this data object by calling #do_read then #done_read.
|
200
209
|
def read(io)
|
201
|
-
|
202
|
-
io = StringIO.new(io) if io.respond_to?(:to_str)
|
203
|
-
|
204
|
-
# remove previous method to prevent warnings
|
205
|
-
class << io
|
206
|
-
remove_method(:bindata_mark) if method_defined?(:bindata_mark)
|
207
|
-
end
|
208
|
-
|
209
|
-
# remember the current position in the IO object
|
210
|
-
io.instance_eval "def bindata_mark; #{io.pos}; end"
|
210
|
+
io = BinData::IO.new(io) unless BinData::IO === io
|
211
211
|
|
212
212
|
do_read(io)
|
213
213
|
done_read
|
@@ -216,14 +216,49 @@ module BinData
|
|
216
216
|
|
217
217
|
# Reads the value for this data from +io+.
|
218
218
|
def do_read(io)
|
219
|
+
raise ArgumentError, "io must be a BinData::IO" unless BinData::IO === io
|
220
|
+
|
219
221
|
clear
|
220
222
|
check_offset(io)
|
221
|
-
|
223
|
+
|
224
|
+
if eval_param(:readwrite)
|
225
|
+
_do_read(io)
|
226
|
+
end
|
222
227
|
end
|
223
228
|
|
224
|
-
# Writes the value for this data to +io
|
229
|
+
# Writes the value for this data to +io+ by calling #do_write.
|
225
230
|
def write(io)
|
226
|
-
|
231
|
+
io = BinData::IO.new(io) unless BinData::IO === io
|
232
|
+
|
233
|
+
do_write(io)
|
234
|
+
io.flush
|
235
|
+
self
|
236
|
+
end
|
237
|
+
|
238
|
+
|
239
|
+
# Writes the value for this data to +io+.
|
240
|
+
def do_write(io)
|
241
|
+
raise ArgumentError, "io must be a BinData::IO" unless BinData::IO === io
|
242
|
+
|
243
|
+
if eval_param(:readwrite)
|
244
|
+
_do_write(io)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# Returns the number of bytes it will take to write this data by calling
|
249
|
+
# #do_num_bytes.
|
250
|
+
def num_bytes(what = nil)
|
251
|
+
num = do_num_bytes(what)
|
252
|
+
num.ceil
|
253
|
+
end
|
254
|
+
|
255
|
+
# Returns the number of bytes it will take to write this data.
|
256
|
+
def do_num_bytes(what = nil)
|
257
|
+
if eval_param(:readwrite)
|
258
|
+
_do_num_bytes(what)
|
259
|
+
else
|
260
|
+
0
|
261
|
+
end
|
227
262
|
end
|
228
263
|
|
229
264
|
# Returns the string representation of this data object.
|
@@ -234,11 +269,6 @@ module BinData
|
|
234
269
|
io.read
|
235
270
|
end
|
236
271
|
|
237
|
-
# Returns the number of bytes it will take to write this data.
|
238
|
-
def num_bytes(what = nil)
|
239
|
-
(eval_param(:readwrite) != false) ? _num_bytes(what) : 0
|
240
|
-
end
|
241
|
-
|
242
272
|
# Return a human readable representation of this object.
|
243
273
|
def inspect
|
244
274
|
snapshot.inspect
|
@@ -277,7 +307,7 @@ module BinData
|
|
277
307
|
# be called from #do_read before performing the reading.
|
278
308
|
def check_offset(io)
|
279
309
|
if has_param?(:check_offset)
|
280
|
-
actual_offset = io.
|
310
|
+
actual_offset = io.offset
|
281
311
|
expected = eval_param(:check_offset, :offset => actual_offset)
|
282
312
|
|
283
313
|
if not expected
|
@@ -287,12 +317,12 @@ module BinData
|
|
287
317
|
"expected '#{expected}'"
|
288
318
|
end
|
289
319
|
elsif has_param?(:adjust_offset)
|
290
|
-
actual_offset = io.
|
320
|
+
actual_offset = io.offset
|
291
321
|
expected = eval_param(:adjust_offset)
|
292
322
|
if actual_offset != expected
|
293
323
|
begin
|
294
324
|
seek = expected - actual_offset
|
295
|
-
io.
|
325
|
+
io.seekbytes(seek)
|
296
326
|
warn "adjusting stream position by #{seek} bytes" if $VERBOSE
|
297
327
|
rescue
|
298
328
|
# could not seek so raise an error
|
@@ -317,46 +347,46 @@ module BinData
|
|
317
347
|
raise NotImplementedError
|
318
348
|
end
|
319
349
|
|
320
|
-
#
|
321
|
-
|
350
|
+
# Returns whether this data object contains a single value. Single
|
351
|
+
# value data objects respond to <tt>#value</tt> and <tt>#value=</tt>.
|
352
|
+
def single_value?
|
322
353
|
raise NotImplementedError
|
323
354
|
end
|
324
355
|
|
325
|
-
#
|
326
|
-
|
356
|
+
# Returns a list of the names of all fields accessible through this
|
357
|
+
# object.
|
358
|
+
def field_names
|
327
359
|
raise NotImplementedError
|
328
360
|
end
|
329
361
|
|
330
|
-
#
|
331
|
-
def
|
362
|
+
# Returns a snapshot of this data object.
|
363
|
+
def snapshot
|
332
364
|
raise NotImplementedError
|
333
365
|
end
|
334
366
|
|
335
|
-
#
|
336
|
-
def
|
367
|
+
# To be called after calling #do_read.
|
368
|
+
def done_read
|
337
369
|
raise NotImplementedError
|
338
370
|
end
|
339
371
|
|
340
|
-
#
|
341
|
-
def
|
372
|
+
# Reads the data for this data object from +io+.
|
373
|
+
def _do_read(io)
|
342
374
|
raise NotImplementedError
|
343
375
|
end
|
344
376
|
|
345
|
-
#
|
346
|
-
|
347
|
-
def single_value?
|
377
|
+
# Writes the value for this data to +io+.
|
378
|
+
def _do_write(io)
|
348
379
|
raise NotImplementedError
|
349
380
|
end
|
350
381
|
|
351
|
-
# Returns
|
352
|
-
|
353
|
-
def field_names
|
382
|
+
# Returns the number of bytes it will take to write this data.
|
383
|
+
def _do_num_bytes
|
354
384
|
raise NotImplementedError
|
355
385
|
end
|
356
386
|
|
357
387
|
# Set visibility requirements of methods to implement
|
358
|
-
public :clear, :
|
359
|
-
private :_do_read, :
|
388
|
+
public :clear, :single_value?, :field_names, :snapshot, :done_read
|
389
|
+
private :_do_read, :_do_write, :_do_num_bytes
|
360
390
|
|
361
391
|
# End To be implemented by subclasses
|
362
392
|
###########################################################################
|