jbangert-bindata 1.5.0
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.
- data/.gitignore +1 -0
- data/BSDL +22 -0
- data/COPYING +52 -0
- data/ChangeLog.rdoc +204 -0
- data/Gemfile +2 -0
- data/INSTALL +11 -0
- data/NEWS.rdoc +164 -0
- data/README.md +54 -0
- data/Rakefile +13 -0
- data/bindata.gemspec +31 -0
- data/doc/manual.haml +407 -0
- data/doc/manual.md +1649 -0
- data/examples/NBT.txt +149 -0
- data/examples/gzip.rb +161 -0
- data/examples/ip_address.rb +22 -0
- data/examples/list.rb +124 -0
- data/examples/nbt.rb +178 -0
- data/lib/bindata.rb +33 -0
- data/lib/bindata/alignment.rb +83 -0
- data/lib/bindata/array.rb +335 -0
- data/lib/bindata/base.rb +388 -0
- data/lib/bindata/base_primitive.rb +214 -0
- data/lib/bindata/bits.rb +87 -0
- data/lib/bindata/choice.rb +216 -0
- data/lib/bindata/count_bytes_remaining.rb +35 -0
- data/lib/bindata/deprecated.rb +50 -0
- data/lib/bindata/dsl.rb +312 -0
- data/lib/bindata/float.rb +80 -0
- data/lib/bindata/int.rb +184 -0
- data/lib/bindata/io.rb +274 -0
- data/lib/bindata/lazy.rb +105 -0
- data/lib/bindata/offset.rb +91 -0
- data/lib/bindata/params.rb +135 -0
- data/lib/bindata/primitive.rb +135 -0
- data/lib/bindata/record.rb +110 -0
- data/lib/bindata/registry.rb +92 -0
- data/lib/bindata/rest.rb +35 -0
- data/lib/bindata/sanitize.rb +290 -0
- data/lib/bindata/skip.rb +48 -0
- data/lib/bindata/string.rb +145 -0
- data/lib/bindata/stringz.rb +96 -0
- data/lib/bindata/struct.rb +388 -0
- data/lib/bindata/trace.rb +94 -0
- data/lib/bindata/version.rb +3 -0
- data/setup.rb +1585 -0
- data/spec/alignment_spec.rb +61 -0
- data/spec/array_spec.rb +331 -0
- data/spec/base_primitive_spec.rb +238 -0
- data/spec/base_spec.rb +376 -0
- data/spec/bits_spec.rb +163 -0
- data/spec/choice_spec.rb +263 -0
- data/spec/count_bytes_remaining_spec.rb +38 -0
- data/spec/deprecated_spec.rb +31 -0
- data/spec/example.rb +21 -0
- data/spec/float_spec.rb +37 -0
- data/spec/int_spec.rb +216 -0
- data/spec/io_spec.rb +352 -0
- data/spec/lazy_spec.rb +217 -0
- data/spec/primitive_spec.rb +202 -0
- data/spec/record_spec.rb +530 -0
- data/spec/registry_spec.rb +108 -0
- data/spec/rest_spec.rb +26 -0
- data/spec/skip_spec.rb +27 -0
- data/spec/spec_common.rb +58 -0
- data/spec/string_spec.rb +300 -0
- data/spec/stringz_spec.rb +118 -0
- data/spec/struct_spec.rb +350 -0
- data/spec/system_spec.rb +380 -0
- data/tasks/manual.rake +36 -0
- data/tasks/rspec.rake +17 -0
- metadata +208 -0
@@ -0,0 +1,202 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "spec_common"))
|
4
|
+
require 'bindata'
|
5
|
+
|
6
|
+
describe BinData::Primitive do
|
7
|
+
it "is not registered" do
|
8
|
+
expect {
|
9
|
+
BinData::RegisteredClasses.lookup("Primitive")
|
10
|
+
}.to raise_error(BinData::UnRegisteredTypeError)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe BinData::Primitive, "all subclasses" do
|
15
|
+
class SubClassOfPrimitive < BinData::Primitive
|
16
|
+
expose_methods_for_testing
|
17
|
+
end
|
18
|
+
|
19
|
+
subject { SubClassOfPrimitive.new }
|
20
|
+
|
21
|
+
it "raise errors on unimplemented methods" do
|
22
|
+
expect { subject.set(nil) }.to raise_error(NotImplementedError)
|
23
|
+
expect { subject.get }.to raise_error(NotImplementedError)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe BinData::Primitive, "when defining with errors" do
|
28
|
+
it "fails on non registered types" do
|
29
|
+
lambda {
|
30
|
+
class BadTypePrimitive < BinData::Primitive
|
31
|
+
non_registered_type :a
|
32
|
+
end
|
33
|
+
}.should raise_error_on_line(TypeError, 2) { |err|
|
34
|
+
err.message.should == "unknown type 'non_registered_type' in #{BadTypePrimitive}"
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
it "fails on duplicate names" do
|
39
|
+
lambda {
|
40
|
+
class DuplicateNamePrimitive < BinData::Primitive
|
41
|
+
int8 :a
|
42
|
+
int8 :b
|
43
|
+
int8 :a
|
44
|
+
end
|
45
|
+
}.should raise_error_on_line(SyntaxError, 4) { |err|
|
46
|
+
err.message.should == "duplicate field 'a' in #{DuplicateNamePrimitive}"
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
it "fails when field name shadows an existing method" do
|
51
|
+
lambda {
|
52
|
+
class ExistingNamePrimitive < BinData::Primitive
|
53
|
+
int8 :object_id
|
54
|
+
end
|
55
|
+
}.should raise_error_on_line(NameError, 2) { |err|
|
56
|
+
err.message.should == "field 'object_id' shadows an existing method in #{ExistingNamePrimitive}"
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
it "fails on unknown endian" do
|
61
|
+
lambda {
|
62
|
+
class BadEndianPrimitive < BinData::Primitive
|
63
|
+
endian 'a bad value'
|
64
|
+
end
|
65
|
+
}.should raise_error_on_line(ArgumentError, 2) { |err|
|
66
|
+
err.message.should == "unknown value for endian 'a bad value' in #{BadEndianPrimitive}"
|
67
|
+
}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe BinData::Primitive do
|
72
|
+
class PrimitiveWithEndian < BinData::Primitive
|
73
|
+
endian :little
|
74
|
+
int16 :a
|
75
|
+
def get; self.a; end
|
76
|
+
def set(v); self.a = v; end
|
77
|
+
end
|
78
|
+
|
79
|
+
subject { PrimitiveWithEndian.new }
|
80
|
+
|
81
|
+
it "assigns value" do
|
82
|
+
subject.value = 5
|
83
|
+
subject.value.should == 5
|
84
|
+
end
|
85
|
+
|
86
|
+
it "produces binary string" do
|
87
|
+
subject.assign(5)
|
88
|
+
subject.to_binary_s.should == "\x05\x00"
|
89
|
+
end
|
90
|
+
|
91
|
+
it "reads value" do
|
92
|
+
subject.read("\x00\x01")
|
93
|
+
subject.should == 0x100
|
94
|
+
end
|
95
|
+
|
96
|
+
it "accepts standard parameters" do
|
97
|
+
subject = PrimitiveWithEndian.new(:initial_value => 2)
|
98
|
+
subject.to_binary_s.should == "\x02\x00"
|
99
|
+
end
|
100
|
+
|
101
|
+
it "returns num_bytes" do
|
102
|
+
subject.num_bytes.should == 2
|
103
|
+
end
|
104
|
+
|
105
|
+
it "raises error on missing methods" do
|
106
|
+
expect {
|
107
|
+
subject.does_not_exist
|
108
|
+
}.to raise_error(NoMethodError)
|
109
|
+
end
|
110
|
+
|
111
|
+
it "uses read value whilst reading" do
|
112
|
+
subject = PrimitiveWithEndian.new(:value => 2)
|
113
|
+
subject.read "\x05\x00"
|
114
|
+
subject.should == 2
|
115
|
+
|
116
|
+
subject.stub(:reading?).and_return(true)
|
117
|
+
subject.should == 5
|
118
|
+
end
|
119
|
+
|
120
|
+
it "behaves as primitive" do
|
121
|
+
subject.assign(5)
|
122
|
+
(2 + subject).should == 7
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe BinData::Primitive, "requiring custom parameters" do
|
127
|
+
class PrimitiveWithCustom < BinData::Primitive
|
128
|
+
int8 :a, :initial_value => :iv
|
129
|
+
def get; self.a; end
|
130
|
+
def set(v); self.a = v; end
|
131
|
+
end
|
132
|
+
|
133
|
+
it "passes parameters correctly" do
|
134
|
+
subject = PrimitiveWithCustom.new(:iv => 5)
|
135
|
+
subject.should == 5
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe BinData::Primitive, "with custom mandatory parameters" do
|
140
|
+
class MandatoryPrimitive < BinData::Primitive
|
141
|
+
mandatory_parameter :arg1
|
142
|
+
|
143
|
+
uint8 :a, :value => :arg1
|
144
|
+
def get; self.a; end
|
145
|
+
def set(v); self.a = v; end
|
146
|
+
end
|
147
|
+
|
148
|
+
it "raises error if mandatory parameter is not supplied" do
|
149
|
+
expect { MandatoryPrimitive.new }.to raise_error(ArgumentError)
|
150
|
+
end
|
151
|
+
|
152
|
+
it "uses mandatory parameter" do
|
153
|
+
subject = MandatoryPrimitive.new(:arg1 => 5)
|
154
|
+
subject.should == 5
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe BinData::Primitive, "with custom default parameters" do
|
159
|
+
class DefaultPrimitive < BinData::Primitive
|
160
|
+
default_parameter :arg1 => 5
|
161
|
+
|
162
|
+
uint8 :a, :value => :arg1
|
163
|
+
def get; self.a; end
|
164
|
+
def set(v); self.a = v; end
|
165
|
+
end
|
166
|
+
|
167
|
+
it "does not raise error if default parameter is not supplied" do
|
168
|
+
expect { DefaultPrimitive.new }.not_to raise_error(ArgumentError)
|
169
|
+
end
|
170
|
+
|
171
|
+
it "uses default parameter" do
|
172
|
+
subject = DefaultPrimitive.new
|
173
|
+
subject.should == 5
|
174
|
+
end
|
175
|
+
|
176
|
+
it "overrides default parameter" do
|
177
|
+
subject = DefaultPrimitive.new(:arg1 => 7)
|
178
|
+
subject.should == 7
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
describe BinData::Primitive, "subclassed with default parameter" do
|
183
|
+
class ParentDerivedPrimitive < BinData::Primitive
|
184
|
+
uint16be :a
|
185
|
+
def get; self.a; end
|
186
|
+
def set(v); self.a = v; end
|
187
|
+
end
|
188
|
+
|
189
|
+
class ChildDerivedPrimitive < ParentDerivedPrimitive
|
190
|
+
default_parameter :initial_value => 5
|
191
|
+
end
|
192
|
+
|
193
|
+
it "overrides initial_value" do
|
194
|
+
a = ChildDerivedPrimitive.new(:initial_value => 7)
|
195
|
+
a.to_binary_s.should == "\000\007"
|
196
|
+
end
|
197
|
+
|
198
|
+
it "uses default parameter" do
|
199
|
+
a = ChildDerivedPrimitive.new
|
200
|
+
a.to_binary_s.should == "\000\005"
|
201
|
+
end
|
202
|
+
end
|
data/spec/record_spec.rb
ADDED
@@ -0,0 +1,530 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "spec_common"))
|
4
|
+
require 'bindata'
|
5
|
+
|
6
|
+
describe BinData::Record do
|
7
|
+
it "is not registered" do
|
8
|
+
expect {
|
9
|
+
BinData::RegisteredClasses.lookup("Record")
|
10
|
+
}.to raise_error(BinData::UnRegisteredTypeError)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe BinData::Record, "when defining with errors" do
|
15
|
+
it "fails on non registered types" do
|
16
|
+
lambda {
|
17
|
+
class BadTypeRecord < BinData::Record
|
18
|
+
non_registered_type :a
|
19
|
+
end
|
20
|
+
}.should raise_error_on_line(TypeError, 2) { |err|
|
21
|
+
err.message.should == "unknown type 'non_registered_type' in #{BadTypeRecord}"
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
it "gives correct error message for non registered nested types" do
|
26
|
+
lambda {
|
27
|
+
class BadNestedTypeRecord < BinData::Record
|
28
|
+
array :a, :type => :non_registered_type
|
29
|
+
end
|
30
|
+
}.should raise_error_on_line(TypeError, 2) { |err|
|
31
|
+
err.message.should == "unknown type 'non_registered_type' in #{BadNestedTypeRecord}"
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
it "gives correct error message for non registered nested types in blocks" do
|
36
|
+
lambda {
|
37
|
+
class BadNestedTypeInBlockRecord < BinData::Record
|
38
|
+
array :a do
|
39
|
+
non_registered_type
|
40
|
+
end
|
41
|
+
end
|
42
|
+
}.should raise_error_on_line(TypeError, 3) { |err|
|
43
|
+
err.message.should == "unknown type 'non_registered_type' in #{BinData::Array}"
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
it "fails on nested choice when missing names" do
|
48
|
+
lambda {
|
49
|
+
class MissingChoiceNamesRecord < BinData::Record
|
50
|
+
choice do
|
51
|
+
int8 :a
|
52
|
+
int8
|
53
|
+
end
|
54
|
+
end
|
55
|
+
}.should raise_error_on_line(SyntaxError, 4) { |err|
|
56
|
+
err.message.should == "fields must either all have names, or none must have names in BinData::Choice"
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
it "fails on malformed names" do
|
61
|
+
lambda {
|
62
|
+
class MalformedNameRecord < BinData::Record
|
63
|
+
int8 :a
|
64
|
+
int8 "45"
|
65
|
+
end
|
66
|
+
}.should raise_error_on_line(NameError, 3) { |err|
|
67
|
+
err.message.should == "field '45' is an illegal fieldname in #{MalformedNameRecord}"
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
it "fails on duplicate names" do
|
72
|
+
lambda {
|
73
|
+
class DuplicateNameRecord < BinData::Record
|
74
|
+
int8 :a
|
75
|
+
int8 :b
|
76
|
+
int8 :a
|
77
|
+
end
|
78
|
+
}.should raise_error_on_line(SyntaxError, 4) { |err|
|
79
|
+
err.message.should == "duplicate field 'a' in #{DuplicateNameRecord}"
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
it "fails on reserved names" do
|
84
|
+
lambda {
|
85
|
+
class ReservedNameRecord < BinData::Record
|
86
|
+
int8 :a
|
87
|
+
int8 :invert # from Hash.instance_methods
|
88
|
+
end
|
89
|
+
}.should raise_error_on_line(NameError, 3) { |err|
|
90
|
+
err.message.should == "field 'invert' is a reserved name in #{ReservedNameRecord}"
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
it "fails when field name shadows an existing method" do
|
95
|
+
lambda {
|
96
|
+
class ExistingNameRecord < BinData::Record
|
97
|
+
int8 :object_id
|
98
|
+
end
|
99
|
+
}.should raise_error_on_line(NameError, 2) { |err|
|
100
|
+
err.message.should == "field 'object_id' shadows an existing method in #{ExistingNameRecord}"
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
it "fails on unknown endian" do
|
105
|
+
lambda {
|
106
|
+
class BadEndianRecord < BinData::Record
|
107
|
+
endian 'a bad value'
|
108
|
+
end
|
109
|
+
}.should raise_error_on_line(ArgumentError, 2) { |err|
|
110
|
+
err.message.should == "unknown value for endian 'a bad value' in #{BadEndianRecord}"
|
111
|
+
}
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe BinData::Record, "with anonymous fields" do
|
116
|
+
class AnonymousRecord < BinData::Record
|
117
|
+
int8 'a', :initial_value => 10
|
118
|
+
int8 ''
|
119
|
+
int8 nil
|
120
|
+
int8
|
121
|
+
int8 :value => :a
|
122
|
+
end
|
123
|
+
|
124
|
+
subject { AnonymousRecord.new }
|
125
|
+
|
126
|
+
it "only shows non anonymous fields" do
|
127
|
+
subject.field_names.should == ["a"]
|
128
|
+
end
|
129
|
+
|
130
|
+
it "does not include anonymous fields in snapshot" do
|
131
|
+
subject.a = 5
|
132
|
+
subject.snapshot.should == {"a" => 5}
|
133
|
+
end
|
134
|
+
|
135
|
+
it "writes anonymous fields" do
|
136
|
+
str = "\001\002\003\004\005"
|
137
|
+
subject.read(str)
|
138
|
+
subject.a.clear
|
139
|
+
subject.to_binary_s.should == "\012\002\003\004\012"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe BinData::Record, "with hidden fields" do
|
144
|
+
class HiddenRecord < BinData::Record
|
145
|
+
hide :b, :c
|
146
|
+
int8 :a
|
147
|
+
int8 'b', :initial_value => 10
|
148
|
+
int8 :c
|
149
|
+
int8 :d, :value => :b
|
150
|
+
end
|
151
|
+
|
152
|
+
subject { HiddenRecord.new }
|
153
|
+
|
154
|
+
it "only shows fields that aren't hidden" do
|
155
|
+
subject.field_names.should == ["a", "d"]
|
156
|
+
end
|
157
|
+
|
158
|
+
it "accesses hidden fields directly" do
|
159
|
+
subject.b.should == 10
|
160
|
+
subject.c = 15
|
161
|
+
subject.c.should == 15
|
162
|
+
|
163
|
+
subject.should respond_to(:b=)
|
164
|
+
end
|
165
|
+
|
166
|
+
it "does not include hidden fields in snapshot" do
|
167
|
+
subject.b = 5
|
168
|
+
subject.snapshot.should == {"a" => 0, "d" => 5}
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe BinData::Record, "with multiple fields" do
|
173
|
+
class MultiFieldRecord < BinData::Record
|
174
|
+
int8 :a
|
175
|
+
int8 :b
|
176
|
+
end
|
177
|
+
|
178
|
+
subject { MultiFieldRecord.new(:a => 1, :b => 2) }
|
179
|
+
|
180
|
+
it "returns num_bytes" do
|
181
|
+
subject.a.num_bytes.should == 1
|
182
|
+
subject.b.num_bytes.should == 1
|
183
|
+
subject.num_bytes.should == 2
|
184
|
+
end
|
185
|
+
|
186
|
+
it "identifies accepted parameters" do
|
187
|
+
BinData::Record.accepted_parameters.all.should include(:hide)
|
188
|
+
BinData::Record.accepted_parameters.all.should include(:endian)
|
189
|
+
end
|
190
|
+
|
191
|
+
it "clears" do
|
192
|
+
subject.a = 6
|
193
|
+
subject.clear
|
194
|
+
subject.should be_clear
|
195
|
+
end
|
196
|
+
|
197
|
+
it "clears individual elements" do
|
198
|
+
subject.a = 6
|
199
|
+
subject.b = 7
|
200
|
+
subject.a.clear
|
201
|
+
subject.a.should be_clear
|
202
|
+
subject.b.should_not be_clear
|
203
|
+
end
|
204
|
+
|
205
|
+
it "writes ordered" do
|
206
|
+
subject.to_binary_s.should == "\x01\x02"
|
207
|
+
end
|
208
|
+
|
209
|
+
it "reads ordered" do
|
210
|
+
subject.read("\x03\x04")
|
211
|
+
|
212
|
+
subject.a.should == 3
|
213
|
+
subject.b.should == 4
|
214
|
+
end
|
215
|
+
|
216
|
+
it "returns a snapshot" do
|
217
|
+
snap = subject.snapshot
|
218
|
+
snap.a.should == 1
|
219
|
+
snap.b.should == 2
|
220
|
+
snap.should == { "a" => 1, "b" => 2 }
|
221
|
+
end
|
222
|
+
|
223
|
+
it "returns field_names" do
|
224
|
+
subject.field_names.should == ["a", "b"]
|
225
|
+
end
|
226
|
+
|
227
|
+
it "fails on unknown method call" do
|
228
|
+
expect { subject.does_not_exist }.to raise_error(NoMethodError)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
describe BinData::Record, "with nested structs" do
|
233
|
+
class NestedStructRecord < BinData::Record
|
234
|
+
int8 :a, :initial_value => 6
|
235
|
+
struct :b, :the_val => :a do
|
236
|
+
hide :w
|
237
|
+
int8 :w, :initial_value => 3
|
238
|
+
int8 :x, :value => :the_val
|
239
|
+
end
|
240
|
+
struct :c do
|
241
|
+
int8 :y, :value => lambda { b.w }
|
242
|
+
int8 :z
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
subject { NestedStructRecord.new }
|
247
|
+
|
248
|
+
it "includes nested field names" do
|
249
|
+
subject.field_names.should == ["a", "b", "c"]
|
250
|
+
end
|
251
|
+
|
252
|
+
it "hides nested field names" do
|
253
|
+
subject.b.field_names.should == ["x"]
|
254
|
+
end
|
255
|
+
|
256
|
+
it "accesses nested fields" do
|
257
|
+
subject.a.should == 6
|
258
|
+
subject.b.w.should == 3
|
259
|
+
subject.b.x.should == 6
|
260
|
+
subject.c.y.should == 3
|
261
|
+
end
|
262
|
+
|
263
|
+
it "returns correct offset" do
|
264
|
+
subject.offset.should == 0
|
265
|
+
subject.b.offset.should == 1
|
266
|
+
subject.b.w.offset.should == 1
|
267
|
+
subject.c.offset.should == 3
|
268
|
+
subject.c.z.offset.should == 4
|
269
|
+
end
|
270
|
+
|
271
|
+
it "returns correct rel_offset" do
|
272
|
+
subject.rel_offset.should == 0
|
273
|
+
subject.b.rel_offset.should == 1
|
274
|
+
subject.b.w.rel_offset.should == 0
|
275
|
+
subject.c.rel_offset.should == 3
|
276
|
+
subject.c.z.rel_offset.should == 1
|
277
|
+
end
|
278
|
+
|
279
|
+
it "assigns nested fields" do
|
280
|
+
subject.assign(:a => 2, :b => {:w => 4})
|
281
|
+
subject.a.should == 2
|
282
|
+
subject.b.w.should == 4
|
283
|
+
subject.b.x.should == 2
|
284
|
+
subject.c.y.should == 4
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
describe BinData::Record, "with nested array of primitives" do
|
289
|
+
class NestedPrimitiveArrayRecord < BinData::Record
|
290
|
+
array :a, :initial_length => 3 do
|
291
|
+
uint8 :value => lambda { index }
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
subject { NestedPrimitiveArrayRecord.new }
|
296
|
+
|
297
|
+
it "uses block as :type" do
|
298
|
+
subject.snapshot.should == {"a" => [0, 1, 2]}
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
describe BinData::Record, "with nested array of structs" do
|
303
|
+
class NestedStructArrayRecord < BinData::Record
|
304
|
+
array :a do
|
305
|
+
uint8 :b
|
306
|
+
uint8 :c
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
subject { NestedStructArrayRecord.new }
|
311
|
+
|
312
|
+
it "uses block as struct for :type" do
|
313
|
+
subject.a[0].b = 2
|
314
|
+
subject.snapshot.should == {"a" => [{"b" => 2, "c" => 0}]}
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
describe BinData::Record, "with nested choice with implied keys" do
|
319
|
+
class NestedChoiceWithImpliedKeysRecord < BinData::Record
|
320
|
+
choice :a, :selection => 1 do
|
321
|
+
uint8 :value => 1
|
322
|
+
uint8 :value => 2
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
subject { NestedChoiceWithImpliedKeysRecord.new }
|
327
|
+
|
328
|
+
its(:a) { should == 2 }
|
329
|
+
end
|
330
|
+
|
331
|
+
describe BinData::Record, "with nested choice with explicit keys" do
|
332
|
+
class NestedChoiceWithKeysRecord < BinData::Record
|
333
|
+
choice :a, :selection => 5 do
|
334
|
+
uint8 3, :value => 1
|
335
|
+
uint8 5, :value => 2
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
subject { NestedChoiceWithKeysRecord.new }
|
340
|
+
|
341
|
+
its(:a) { should == 2 }
|
342
|
+
end
|
343
|
+
|
344
|
+
describe BinData::Record, "with nested choice with names" do
|
345
|
+
class NestedChoiceWithNamesRecord < BinData::Record
|
346
|
+
choice :a, :selection => "b" do
|
347
|
+
uint8 "b", :value => 1
|
348
|
+
uint8 "c", :value => 2
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
subject { NestedChoiceWithNamesRecord.new }
|
353
|
+
|
354
|
+
its(:a) { should == 1 }
|
355
|
+
end
|
356
|
+
|
357
|
+
describe BinData::Record, "with an endian defined" do
|
358
|
+
class RecordWithEndian < BinData::Record
|
359
|
+
endian :little
|
360
|
+
|
361
|
+
uint16 :a
|
362
|
+
float :b
|
363
|
+
array :c, :initial_length => 2 do
|
364
|
+
int8
|
365
|
+
end
|
366
|
+
choice :d, :selection => 1 do
|
367
|
+
uint16
|
368
|
+
uint32
|
369
|
+
end
|
370
|
+
struct :e do
|
371
|
+
uint16 :f
|
372
|
+
uint32be :g
|
373
|
+
end
|
374
|
+
struct :h do
|
375
|
+
endian :big
|
376
|
+
struct :i do
|
377
|
+
uint16 :j
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
subject { RecordWithEndian.new }
|
383
|
+
|
384
|
+
it "uses correct endian" do
|
385
|
+
subject.a = 1
|
386
|
+
subject.b = 2.0
|
387
|
+
subject.c[0] = 3
|
388
|
+
subject.c[1] = 4
|
389
|
+
subject.d = 5
|
390
|
+
subject.e.f = 6
|
391
|
+
subject.e.g = 7
|
392
|
+
subject.h.i.j = 8
|
393
|
+
|
394
|
+
expected = [1, 2.0, 3, 4, 5, 6, 7, 8].pack('veCCVvNn')
|
395
|
+
|
396
|
+
subject.to_binary_s.should == expected
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
describe BinData::Record, "defined recursively" do
|
401
|
+
class RecursiveRecord < BinData::Record
|
402
|
+
endian :big
|
403
|
+
uint16 :val
|
404
|
+
uint8 :has_nxt, :value => lambda { nxt.clear? ? 0 : 1 }
|
405
|
+
recursive_record :nxt, :onlyif => lambda { has_nxt > 0 }
|
406
|
+
end
|
407
|
+
|
408
|
+
it "can be created" do
|
409
|
+
subject = RecursiveRecord.new
|
410
|
+
end
|
411
|
+
|
412
|
+
it "reads" do
|
413
|
+
str = "\x00\x01\x01\x00\x02\x01\x00\x03\x00"
|
414
|
+
subject = RecursiveRecord.read(str)
|
415
|
+
subject.val.should == 1
|
416
|
+
subject.nxt.val.should == 2
|
417
|
+
subject.nxt.nxt.val.should == 3
|
418
|
+
end
|
419
|
+
|
420
|
+
it "is assignable on demand" do
|
421
|
+
subject = RecursiveRecord.new
|
422
|
+
subject.val = 13
|
423
|
+
subject.nxt.val = 14
|
424
|
+
subject.nxt.nxt.val = 15
|
425
|
+
end
|
426
|
+
|
427
|
+
it "writes" do
|
428
|
+
subject = RecursiveRecord.new
|
429
|
+
subject.val = 5
|
430
|
+
subject.nxt.val = 6
|
431
|
+
subject.nxt.nxt.val = 7
|
432
|
+
subject.to_binary_s.should == "\x00\x05\x01\x00\x06\x01\x00\x07\x00"
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
describe BinData::Record, "with custom mandatory parameters" do
|
437
|
+
class MandatoryRecord < BinData::Record
|
438
|
+
mandatory_parameter :arg1
|
439
|
+
|
440
|
+
uint8 :a, :value => :arg1
|
441
|
+
end
|
442
|
+
|
443
|
+
it "raises error if mandatory parameter is not supplied" do
|
444
|
+
expect { MandatoryRecord.new }.to raise_error(ArgumentError)
|
445
|
+
end
|
446
|
+
|
447
|
+
it "uses mandatory parameter" do
|
448
|
+
subject = MandatoryRecord.new(:arg1 => 5)
|
449
|
+
subject.a.should == 5
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
describe BinData::Record, "with custom default parameters" do
|
454
|
+
class DefaultRecord < BinData::Record
|
455
|
+
default_parameter :arg1 => 5
|
456
|
+
|
457
|
+
uint8 :a, :value => :arg1
|
458
|
+
uint8 :b
|
459
|
+
end
|
460
|
+
|
461
|
+
it "does not raise error if default parameter is not supplied" do
|
462
|
+
expect { DefaultRecord.new }.not_to raise_error(ArgumentError)
|
463
|
+
end
|
464
|
+
|
465
|
+
it "uses default parameter" do
|
466
|
+
subject = DefaultRecord.new
|
467
|
+
subject.a.should == 5
|
468
|
+
end
|
469
|
+
|
470
|
+
it "overrides default parameter" do
|
471
|
+
subject = DefaultRecord.new(:arg1 => 7)
|
472
|
+
subject.a.should == 7
|
473
|
+
end
|
474
|
+
|
475
|
+
it "accepts values" do
|
476
|
+
subject = DefaultRecord.new(:b => 2)
|
477
|
+
subject.b.should == 2
|
478
|
+
end
|
479
|
+
|
480
|
+
it "accepts values and parameters" do
|
481
|
+
subject = DefaultRecord.new({:b => 2}, :arg1 => 3)
|
482
|
+
subject.a.should == 3
|
483
|
+
subject.b.should == 2
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
describe BinData::Record, "with :onlyif" do
|
488
|
+
class OnlyIfRecord < BinData::Record
|
489
|
+
uint8 :a, :initial_value => 3
|
490
|
+
uint8 :b, :initial_value => 5, :onlyif => lambda { a == 3 }
|
491
|
+
uint8 :c, :initial_value => 7, :onlyif => lambda { a != 3 }
|
492
|
+
end
|
493
|
+
|
494
|
+
subject { OnlyIfRecord.new }
|
495
|
+
|
496
|
+
its(:num_bytes) { should == 2 }
|
497
|
+
its(:snapshot) { should == {"a" => 3, "b" => 5} }
|
498
|
+
its(:to_binary_s) { should == "\x03\x05" }
|
499
|
+
|
500
|
+
it "reads as expected" do
|
501
|
+
subject.read("\x01\x02")
|
502
|
+
subject.snapshot.should == {"a" => 1, "c" => 2}
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
describe BinData::Record, "derived classes" do
|
507
|
+
class ParentRecord < BinData::Record
|
508
|
+
uint8 :a
|
509
|
+
end
|
510
|
+
|
511
|
+
class Child1Record < ParentRecord
|
512
|
+
uint8 :b
|
513
|
+
end
|
514
|
+
|
515
|
+
class Child2Record < Child1Record
|
516
|
+
uint8 :c
|
517
|
+
end
|
518
|
+
|
519
|
+
it "does not affect parent" do
|
520
|
+
ParentRecord.new.field_names.should == ["a"]
|
521
|
+
end
|
522
|
+
|
523
|
+
it "inherits fields for first child" do
|
524
|
+
Child1Record.new.field_names.should == ["a", "b"]
|
525
|
+
end
|
526
|
+
|
527
|
+
it "inherits fields for second child" do
|
528
|
+
Child2Record.new.field_names.should == ["a", "b", "c"]
|
529
|
+
end
|
530
|
+
end
|