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.
- checksums.yaml +4 -4
- data/.travis.yml +2 -0
- data/ChangeLog.rdoc +13 -0
- data/NEWS.rdoc +16 -0
- data/README.md +5 -0
- data/bindata.gemspec +1 -0
- data/examples/gzip.rb +1 -1
- data/lib/bindata.rb +1 -1
- data/lib/bindata/array.rb +23 -22
- data/lib/bindata/base.rb +75 -49
- data/lib/bindata/base_primitive.rb +3 -32
- data/lib/bindata/bits.rb +84 -15
- data/lib/bindata/buffer.rb +19 -18
- data/lib/bindata/choice.rb +51 -67
- data/lib/bindata/dsl.rb +268 -161
- data/lib/bindata/framework.rb +0 -11
- data/lib/bindata/int.rb +67 -42
- data/lib/bindata/io.rb +13 -4
- data/lib/bindata/lazy.rb +1 -1
- data/lib/bindata/primitive.rb +9 -8
- data/lib/bindata/record.rb +9 -60
- data/lib/bindata/sanitize.rb +35 -5
- data/lib/bindata/string.rb +33 -34
- data/lib/bindata/struct.rb +166 -109
- data/lib/bindata/version.rb +1 -1
- data/lib/bindata/{deprecated.rb → warnings.rb} +9 -3
- data/test/array_test.rb +3 -3
- data/test/base_primitive_test.rb +0 -34
- data/test/bits_test.rb +38 -34
- data/test/common.rb +4 -0
- data/test/io_test.rb +15 -0
- data/test/lazy_test.rb +11 -3
- data/test/record_test.rb +83 -6
- data/test/struct_test.rb +40 -9
- data/test/system_test.rb +1 -1
- data/test/{deprecated_test.rb → warnings_test.rb} +0 -0
- metadata +18 -4
data/lib/bindata/framework.rb
CHANGED
@@ -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
|
data/lib/bindata/int.rb
CHANGED
@@ -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
|
-
#{
|
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
|
-
|
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
|
79
|
-
|
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
|
83
|
-
|
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
|
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
|
-
"
|
95
|
+
"ints.at(#{idx[i]})"
|
97
96
|
else
|
98
|
-
"(
|
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" : "
|
113
|
+
i.zero? ? "val" : "val >> #{bits_per_word * i}"
|
118
114
|
end
|
119
115
|
vals.reverse! if (endian == :big)
|
120
116
|
|
121
|
-
|
122
|
-
|
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
|
-
|
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 %
|
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 %
|
136
|
-
d = (endian == :big) ? '
|
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) ? '
|
151
|
+
d = (endian == :big) ? 'S>' : 'S<'
|
139
152
|
else
|
140
153
|
d = 'C'
|
141
154
|
end
|
142
155
|
|
143
|
-
|
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,
|
188
|
+
/^Uint(\d+)be$/ => [:big, :unsigned],
|
164
189
|
/^Uint(\d+)le$/ => [:little, :unsigned],
|
165
|
-
/^Int(\d+)be$/
|
166
|
-
/^Int(\d+)le$/
|
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
|
data/lib/bindata/io.rb
CHANGED
@@ -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.
|
191
|
-
#
|
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.
|
226
|
-
#
|
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
|
|
data/lib/bindata/lazy.rb
CHANGED
data/lib/bindata/primitive.rb
CHANGED
@@ -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
|
-
|
63
|
+
extend DSLMixin
|
64
64
|
|
65
65
|
unregister_self
|
66
|
-
dsl_parser
|
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
|
data/lib/bindata/record.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
|
9
|
+
extend DSLMixin
|
28
10
|
|
29
11
|
unregister_self
|
30
|
-
dsl_parser
|
31
|
-
|
32
|
-
|
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
|
-
|
59
|
-
|
60
|
-
end
|
16
|
+
class RecordArgProcessor < StructArgProcessor
|
17
|
+
include MultiFieldArgSeparator
|
61
18
|
|
62
|
-
|
63
|
-
|
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
|
data/lib/bindata/sanitize.rb
CHANGED
@@ -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
|