bindata 0.9.3 → 0.10.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.
- data/ChangeLog +18 -0
- data/NEWS +59 -0
- data/README +22 -23
- data/TODO +18 -12
- data/examples/gzip.rb +4 -4
- data/lib/bindata.rb +4 -3
- data/lib/bindata/array.rb +202 -132
- data/lib/bindata/base.rb +147 -166
- data/lib/bindata/{single.rb → base_primitive.rb} +82 -56
- data/lib/bindata/bits.rb +31 -770
- data/lib/bindata/choice.rb +157 -82
- data/lib/bindata/float.rb +25 -27
- data/lib/bindata/int.rb +144 -177
- data/lib/bindata/io.rb +59 -49
- data/lib/bindata/lazy.rb +80 -50
- data/lib/bindata/params.rb +134 -26
- data/lib/bindata/{single_value.rb → primitive.rb} +71 -64
- data/lib/bindata/{multi_value.rb → record.rb} +52 -70
- data/lib/bindata/registry.rb +49 -17
- data/lib/bindata/rest.rb +6 -10
- data/lib/bindata/sanitize.rb +55 -70
- data/lib/bindata/string.rb +60 -42
- data/lib/bindata/stringz.rb +34 -35
- data/lib/bindata/struct.rb +197 -152
- data/lib/bindata/trace.rb +35 -0
- data/spec/array_spec.rb +128 -112
- data/spec/{single_spec.rb → base_primitive_spec.rb} +102 -61
- data/spec/base_spec.rb +190 -185
- data/spec/bits_spec.rb +126 -98
- data/spec/choice_spec.rb +89 -98
- data/spec/example.rb +19 -0
- data/spec/float_spec.rb +28 -44
- data/spec/int_spec.rb +217 -127
- data/spec/io_spec.rb +41 -24
- data/spec/lazy_spec.rb +95 -49
- data/spec/primitive_spec.rb +191 -0
- data/spec/{multi_value_spec.rb → record_spec.rb} +124 -89
- data/spec/registry_spec.rb +53 -12
- data/spec/rest_spec.rb +2 -3
- data/spec/sanitize_spec.rb +47 -73
- data/spec/spec_common.rb +13 -1
- data/spec/string_spec.rb +34 -23
- data/spec/stringz_spec.rb +10 -18
- data/spec/struct_spec.rb +91 -63
- data/spec/system_spec.rb +291 -0
- metadata +12 -8
- data/spec/single_value_spec.rb +0 -131
@@ -1,18 +1,19 @@
|
|
1
|
-
require 'bindata/
|
1
|
+
require 'bindata/base_primitive'
|
2
|
+
require 'bindata/registry'
|
2
3
|
require 'bindata/struct'
|
3
4
|
|
4
5
|
module BinData
|
5
|
-
# A
|
6
|
-
# The data type must contain a
|
7
|
-
# that contain multiple values see BinData::
|
6
|
+
# A Primitive is a declarative way to define a new BinData data type.
|
7
|
+
# The data type must contain a primitive value only, i.e numbers or strings.
|
8
|
+
# For new data types that contain multiple values see BinData::Record.
|
8
9
|
#
|
9
|
-
# To define a new data type, set fields as if for
|
10
|
+
# To define a new data type, set fields as if for Record and add a
|
10
11
|
# #get and #set method to extract / convert the data between the fields
|
11
12
|
# and the #value of the object.
|
12
13
|
#
|
13
14
|
# require 'bindata'
|
14
15
|
#
|
15
|
-
# class PascalString < BinData::
|
16
|
+
# class PascalString < BinData::Primitive
|
16
17
|
# uint8 :len, :value => lambda { data.length }
|
17
18
|
# string :data, :read_length => :len
|
18
19
|
#
|
@@ -26,12 +27,12 @@ module BinData
|
|
26
27
|
# end
|
27
28
|
#
|
28
29
|
# ps = PascalString.new(:initial_value => "hello")
|
29
|
-
# ps.
|
30
|
+
# ps.to_binary_s #=> "\005hello"
|
30
31
|
# ps.read("\003abcde")
|
31
32
|
# ps.value #=> "abc"
|
32
33
|
#
|
33
34
|
# # Unsigned 24 bit big endian integer
|
34
|
-
# class Uint24be < BinData::
|
35
|
+
# class Uint24be < BinData::Primitive
|
35
36
|
# uint8 :byte1
|
36
37
|
# uint8 :byte2
|
37
38
|
# uint8 :byte3
|
@@ -56,100 +57,93 @@ module BinData
|
|
56
57
|
#
|
57
58
|
# == Parameters
|
58
59
|
#
|
59
|
-
#
|
60
|
+
# Primitive objects accept all the parameters that BinData::BasePrimitive do.
|
60
61
|
#
|
61
|
-
class
|
62
|
+
class Primitive < BasePrimitive
|
62
63
|
|
63
64
|
class << self
|
64
65
|
|
65
|
-
# Register the names of all subclasses of this class.
|
66
66
|
def inherited(subclass) #:nodoc:
|
67
|
+
# Register the names of all subclasses of this class.
|
67
68
|
register(subclass.name, subclass)
|
68
69
|
end
|
69
70
|
|
70
|
-
# Can this data object self reference itself?
|
71
71
|
def recursive?
|
72
|
+
# A Primitive can possibly self reference itself.
|
72
73
|
true
|
73
74
|
end
|
74
75
|
|
75
|
-
# Returns or sets the endianess of numerics used in this stucture.
|
76
|
-
# Endianess is applied to the fields of this structure.
|
77
|
-
# Valid values are :little and :big.
|
78
76
|
def endian(endian = nil)
|
79
77
|
@endian ||= nil
|
80
78
|
if [:little, :big].include?(endian)
|
81
79
|
@endian = endian
|
82
80
|
elsif endian != nil
|
83
|
-
raise ArgumentError, "unknown value for endian '#{endian}'"
|
81
|
+
raise ArgumentError, "unknown value for endian '#{endian}'", caller(1)
|
84
82
|
end
|
85
83
|
@endian
|
86
84
|
end
|
87
85
|
|
88
|
-
# Returns all stored fields. Should only be called by
|
89
|
-
# #sanitize_parameters
|
90
|
-
def fields
|
91
|
-
@fields || []
|
92
|
-
end
|
93
|
-
|
94
|
-
# Used to define fields for the internal structure.
|
95
86
|
def method_missing(symbol, *args)
|
96
87
|
name, params = args
|
97
88
|
|
98
89
|
type = symbol
|
99
|
-
name =
|
90
|
+
name = name.to_s
|
100
91
|
params ||= {}
|
101
92
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
# check that type is known
|
106
|
-
unless Sanitizer.type_exists?(type, endian)
|
107
|
-
raise TypeError, "unknown type '#{type}' for #{self}", caller
|
108
|
-
end
|
109
|
-
|
110
|
-
# check that name is okay
|
111
|
-
if name != nil
|
112
|
-
# check for duplicate names
|
113
|
-
@fields.each do |t, n, p|
|
114
|
-
if n == name
|
115
|
-
raise SyntaxError, "duplicate field '#{name}' in #{self}", caller
|
116
|
-
end
|
117
|
-
end
|
93
|
+
ensure_type_exists(type)
|
94
|
+
ensure_valid_name(name) unless name.nil?
|
118
95
|
|
119
|
-
|
120
|
-
if self.instance_methods.include?(name)
|
121
|
-
raise NameError.new("", name),
|
122
|
-
"field '#{name}' shadows an existing method", caller
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
# remember this field. These fields will be recalled upon creating
|
127
|
-
# an instance of this class
|
128
|
-
@fields.push([type, name, params])
|
96
|
+
append_field(type, name, params)
|
129
97
|
end
|
130
98
|
|
131
|
-
# Ensures that +params+ is of the form expected by #initialize.
|
132
99
|
def sanitize_parameters!(sanitizer, params)
|
133
100
|
struct_params = {}
|
134
|
-
struct_params[:fields] =
|
135
|
-
struct_params[:endian] =
|
101
|
+
struct_params[:fields] = fields
|
102
|
+
struct_params[:endian] = endian unless endian.nil?
|
136
103
|
|
137
104
|
params[:struct_params] = struct_params
|
138
105
|
|
139
106
|
super(sanitizer, params)
|
140
107
|
end
|
108
|
+
|
109
|
+
#-------------
|
110
|
+
private
|
111
|
+
|
112
|
+
def ensure_type_exists(type)
|
113
|
+
unless RegisteredClasses.is_registered?(type, endian)
|
114
|
+
raise TypeError, "unknown type '#{type}' for #{self}", caller(2)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def ensure_valid_name(name)
|
119
|
+
fields.each do |t, n, p|
|
120
|
+
if n == name
|
121
|
+
raise SyntaxError, "duplicate field '#{name}' in #{self}", caller(4)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
if self.instance_methods.include?(name)
|
125
|
+
raise NameError.new("", name),
|
126
|
+
"field '#{name}' shadows an existing method", caller(2)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def append_field(type, name, params)
|
131
|
+
fields.push([type, name, params])
|
132
|
+
end
|
133
|
+
|
134
|
+
def fields
|
135
|
+
@fields ||= []
|
136
|
+
end
|
141
137
|
end
|
142
138
|
|
143
|
-
|
144
|
-
bindata_mandatory_parameter :struct_params
|
139
|
+
mandatory_parameter :struct_params
|
145
140
|
|
146
141
|
def initialize(params = {}, parent = nil)
|
147
142
|
super(params, parent)
|
148
143
|
|
149
|
-
@struct = BinData::Struct.new(
|
144
|
+
@struct = BinData::Struct.new(get_parameter(:struct_params), self)
|
150
145
|
end
|
151
146
|
|
152
|
-
# Forward method calls to the internal struct.
|
153
147
|
def method_missing(symbol, *args, &block)
|
154
148
|
if @struct.respond_to?(symbol)
|
155
149
|
@struct.__send__(symbol, *args, &block)
|
@@ -158,25 +152,29 @@ module BinData
|
|
158
152
|
end
|
159
153
|
end
|
160
154
|
|
155
|
+
def debug_name_of(child)
|
156
|
+
debug_name + "-internal-"
|
157
|
+
end
|
158
|
+
|
159
|
+
def offset_of(child)
|
160
|
+
offset
|
161
|
+
end
|
162
|
+
|
161
163
|
#---------------
|
162
164
|
private
|
163
165
|
|
164
|
-
# Retrieve a sensible default from the internal struct.
|
165
166
|
def sensible_default
|
166
167
|
get
|
167
168
|
end
|
168
169
|
|
169
|
-
|
170
|
-
def read_val(io)
|
170
|
+
def read_and_return_value(io)
|
171
171
|
@struct.read(io)
|
172
172
|
get
|
173
173
|
end
|
174
174
|
|
175
|
-
|
176
|
-
# string representation.
|
177
|
-
def val_to_str(val)
|
175
|
+
def value_to_binary_string(val)
|
178
176
|
set(val)
|
179
|
-
@struct.
|
177
|
+
@struct.to_binary_s
|
180
178
|
end
|
181
179
|
|
182
180
|
###########################################################################
|
@@ -196,4 +194,13 @@ module BinData
|
|
196
194
|
# To be implemented by subclasses
|
197
195
|
###########################################################################
|
198
196
|
end
|
197
|
+
|
198
|
+
class SingleValue < Primitive
|
199
|
+
class << self
|
200
|
+
def inherited(subclass) #:nodoc:
|
201
|
+
warn "BinData::BasePrimitiveValue is deprecated. Replacing with BinData::Primitive"
|
202
|
+
super
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
199
206
|
end
|
@@ -1,18 +1,18 @@
|
|
1
|
-
require 'bindata/
|
1
|
+
require 'bindata/registry'
|
2
2
|
require 'bindata/struct'
|
3
3
|
|
4
4
|
module BinData
|
5
|
-
# A
|
5
|
+
# A Record is a declarative wrapper around Struct.
|
6
6
|
#
|
7
7
|
# require 'bindata'
|
8
8
|
#
|
9
|
-
# class Tuple < BinData::
|
9
|
+
# class Tuple < BinData::Record
|
10
10
|
# int8 :x
|
11
11
|
# int8 :y
|
12
12
|
# int8 :z
|
13
13
|
# end
|
14
14
|
#
|
15
|
-
# class SomeDataType < BinData::
|
15
|
+
# class SomeDataType < BinData::Record
|
16
16
|
# hide 'a'
|
17
17
|
#
|
18
18
|
# int32le :a
|
@@ -42,50 +42,36 @@ module BinData
|
|
42
42
|
# <tt>:endian</tt>:: Either :little or :big. This specifies the default
|
43
43
|
# endian of any numerics in this struct, or in any
|
44
44
|
# nested data objects.
|
45
|
-
class
|
45
|
+
class Record < BinData::Struct
|
46
46
|
|
47
47
|
class << self
|
48
|
-
extend Parameters
|
49
48
|
|
50
|
-
# Register the names of all subclasses of this class.
|
51
49
|
def inherited(subclass) #:nodoc:
|
50
|
+
# Register the names of all subclasses of this class.
|
52
51
|
register(subclass.name, subclass)
|
53
52
|
end
|
54
53
|
|
55
|
-
# Can this data object self reference itself?
|
56
54
|
def recursive?
|
55
|
+
# A Record can self reference itself.
|
57
56
|
true
|
58
57
|
end
|
59
58
|
|
60
|
-
# Returns or sets the endianess of numerics used in this stucture.
|
61
|
-
# Endianess is applied to the fields of this structure.
|
62
|
-
# Valid values are :little and :big.
|
63
59
|
def endian(endian = nil)
|
64
60
|
@endian ||= nil
|
65
61
|
if [:little, :big].include?(endian)
|
66
62
|
@endian = endian
|
67
63
|
elsif endian != nil
|
68
|
-
raise ArgumentError, "unknown value for endian '#{endian}'"
|
64
|
+
raise ArgumentError, "unknown value for endian '#{endian}'", caller(1)
|
69
65
|
end
|
70
66
|
@endian
|
71
67
|
end
|
72
68
|
|
73
|
-
# Returns the names of any hidden fields in this struct. Any given args
|
74
|
-
# are appended to the hidden list.
|
75
69
|
def hide(*args)
|
76
|
-
# note that fields are stored in an instance variable not a class var
|
77
70
|
@hide ||= []
|
78
71
|
@hide.concat(args.collect { |name| name.to_s })
|
79
72
|
@hide
|
80
73
|
end
|
81
74
|
|
82
|
-
# Returns all stored fields.
|
83
|
-
# Should only be called by #sanitize_parameters
|
84
|
-
def fields
|
85
|
-
@fields ||= []
|
86
|
-
end
|
87
|
-
|
88
|
-
# Used to define fields for this structure.
|
89
75
|
def method_missing(symbol, *args)
|
90
76
|
name, params = args
|
91
77
|
|
@@ -93,78 +79,74 @@ module BinData
|
|
93
79
|
name = name.to_s
|
94
80
|
params ||= {}
|
95
81
|
|
96
|
-
|
97
|
-
|
82
|
+
ensure_type_exists(type)
|
83
|
+
ensure_valid_name(name)
|
84
|
+
|
85
|
+
append_field(type, name, params)
|
86
|
+
end
|
87
|
+
|
88
|
+
def sanitize_parameters!(sanitizer, params)
|
89
|
+
merge_endian!(params)
|
90
|
+
merge_fields!(params)
|
91
|
+
merge_hide!(params)
|
92
|
+
|
93
|
+
super(sanitizer, params)
|
94
|
+
end
|
95
|
+
|
96
|
+
#-------------
|
97
|
+
private
|
98
98
|
|
99
|
-
|
100
|
-
unless
|
101
|
-
raise TypeError, "unknown type '#{type}' for #{self}", caller
|
99
|
+
def ensure_type_exists(type)
|
100
|
+
unless RegisteredClasses.is_registered?(type, endian)
|
101
|
+
raise TypeError, "unknown type '#{type}' for #{self}", caller(2)
|
102
102
|
end
|
103
|
+
end
|
103
104
|
|
104
|
-
|
105
|
+
def ensure_valid_name(name)
|
106
|
+
@fields ||= []
|
105
107
|
@fields.each do |t, n, p|
|
106
108
|
if n == name
|
107
|
-
raise SyntaxError, "duplicate field '#{name}' in #{self}", caller
|
109
|
+
raise SyntaxError, "duplicate field '#{name}' in #{self}", caller(4)
|
108
110
|
end
|
109
111
|
end
|
110
|
-
|
111
|
-
# check that name doesn't shadow an existing method
|
112
112
|
if self.instance_methods.include?(name)
|
113
113
|
raise NameError.new("", name),
|
114
|
-
"field '#{name}' shadows an existing method", caller
|
114
|
+
"field '#{name}' shadows an existing method", caller(2)
|
115
115
|
end
|
116
|
-
|
117
|
-
# check that name isn't reserved
|
118
116
|
if self::RESERVED.include?(name)
|
119
117
|
raise NameError.new("", name),
|
120
|
-
"field '#{name}' is a reserved name", caller
|
118
|
+
"field '#{name}' is a reserved name", caller(2)
|
121
119
|
end
|
120
|
+
end
|
122
121
|
|
123
|
-
|
124
|
-
|
122
|
+
def append_field(type, name, params)
|
123
|
+
@fields ||= []
|
125
124
|
@fields.push([type, name, params])
|
126
125
|
end
|
127
126
|
|
128
|
-
|
129
|
-
def sanitize_parameters!(sanitizer, params)
|
127
|
+
def merge_endian!(params)
|
130
128
|
endian = params[:endian] || self.endian
|
131
|
-
fields = params[:fields] || self.fields
|
132
|
-
hide = params[:hide] || self.hide
|
133
|
-
|
134
129
|
params[:endian] = endian unless endian.nil?
|
135
|
-
params[:fields] = fields
|
136
|
-
params[:hide] = hide
|
137
|
-
|
138
|
-
# add default parameters
|
139
|
-
default_parameters.each do |k,v|
|
140
|
-
params[k] = v unless params.has_key?(k)
|
141
|
-
end
|
142
|
-
|
143
|
-
# ensure mandatory parameters exist
|
144
|
-
mandatory_parameters.each do |prm|
|
145
|
-
if not params.has_key?(prm)
|
146
|
-
raise ArgumentError, "parameter ':#{prm}' must be specified " +
|
147
|
-
"in #{self}"
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
super(sanitizer, params)
|
152
130
|
end
|
153
131
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
args.each { |arg| array << arg.to_sym }
|
159
|
-
array.uniq!
|
132
|
+
def merge_fields!(params)
|
133
|
+
@fields ||= []
|
134
|
+
fields = params[:fields] || @fields || []
|
135
|
+
params[:fields] = fields
|
160
136
|
end
|
161
137
|
|
162
|
-
|
163
|
-
|
138
|
+
def merge_hide!(params)
|
139
|
+
hide = params[:hide] || self.hide
|
140
|
+
params[:hide] = hide
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
164
144
|
|
165
|
-
|
166
|
-
|
167
|
-
|
145
|
+
class MultiValue < Record
|
146
|
+
class << self
|
147
|
+
def inherited(subclass) #:nodoc:
|
148
|
+
warn "BinData::MultiValue is deprecated. Replacing with BinData::Record"
|
149
|
+
super
|
168
150
|
end
|
169
151
|
end
|
170
152
|
end
|
data/lib/bindata/registry.rb
CHANGED
@@ -1,14 +1,34 @@
|
|
1
|
-
require 'singleton'
|
2
|
-
|
3
1
|
module BinData
|
4
2
|
# This registry contains a register of name -> class mappings.
|
3
|
+
#
|
4
|
+
# Names are stored in under_score_style, not camelCase.
|
5
5
|
class Registry
|
6
|
-
include Singleton
|
7
6
|
|
8
7
|
def initialize
|
9
8
|
@registry = {}
|
10
9
|
end
|
11
10
|
|
11
|
+
def register(name, class_to_register)
|
12
|
+
formatted_name = underscore_name(name)
|
13
|
+
warn_if_name_is_already_registered(formatted_name, class_to_register)
|
14
|
+
|
15
|
+
@registry[formatted_name] = class_to_register
|
16
|
+
end
|
17
|
+
|
18
|
+
def lookup(name, endian = nil)
|
19
|
+
key = underscore_name(name.to_s)
|
20
|
+
|
21
|
+
result = @registry[key]
|
22
|
+
if result.nil?
|
23
|
+
result = @registry[merge_key_and_endian(key, endian)]
|
24
|
+
end
|
25
|
+
result
|
26
|
+
end
|
27
|
+
|
28
|
+
def is_registered?(name, endian = nil)
|
29
|
+
lookup(name, endian) != nil
|
30
|
+
end
|
31
|
+
|
12
32
|
# Convert camelCase +name+ to underscore style.
|
13
33
|
def underscore_name(name)
|
14
34
|
name.to_s.sub(/.*::/, "").
|
@@ -18,25 +38,37 @@ module BinData
|
|
18
38
|
downcase
|
19
39
|
end
|
20
40
|
|
21
|
-
|
22
|
-
|
23
|
-
# Returns the converted name
|
24
|
-
def register(name, klass)
|
25
|
-
# convert camelCase name to underscore style
|
26
|
-
key = underscore_name(name)
|
41
|
+
#---------------
|
42
|
+
private
|
27
43
|
|
28
|
-
|
29
|
-
|
30
|
-
|
44
|
+
def merge_key_and_endian(key, endian)
|
45
|
+
result = key
|
46
|
+
if endian != nil
|
47
|
+
if /^u?int\d+$/ =~ key
|
48
|
+
result = key + ((endian == :little) ? "le" : "be")
|
49
|
+
elsif /^(float|double)$/ =~ key
|
50
|
+
result = key + ((endian == :little) ? "_le" : "_be")
|
51
|
+
end
|
31
52
|
end
|
53
|
+
result
|
54
|
+
end
|
32
55
|
|
33
|
-
|
34
|
-
|
56
|
+
def warn_if_name_is_already_registered(name, class_to_register)
|
57
|
+
if $VERBOSE and @registry.has_key?(name)
|
58
|
+
prev_class = @registry[name]
|
59
|
+
warn "warning: replacing registered class #{prev_class} " +
|
60
|
+
"with #{class_to_register}"
|
61
|
+
end
|
35
62
|
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# A singleton registry of all registered classes.
|
66
|
+
RegisteredClasses = Registry.new
|
36
67
|
|
37
|
-
|
38
|
-
def
|
39
|
-
|
68
|
+
class Registry
|
69
|
+
def Registry.instance
|
70
|
+
warn "'Registry.instance' is deprecated. Replacing with 'RegisteredClasses'"
|
71
|
+
RegisteredClasses
|
40
72
|
end
|
41
73
|
end
|
42
74
|
end
|