iostruct 0.5.0 → 0.7.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +71 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +63 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +48 -11
- data/README.md +247 -6
- data/Rakefile +3 -1
- data/iostruct.gemspec +8 -9
- data/lib/iostruct/hash_fmt.rb +112 -0
- data/lib/iostruct/pack_fmt.rb +68 -0
- data/lib/iostruct/version.rb +1 -1
- data/lib/iostruct.rb +159 -103
- data/spec/.rubocop.yml +30 -0
- data/spec/hash_fmt_spec.rb +287 -0
- data/spec/iostruct_spec.rb +295 -22
- metadata +12 -51
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'stringio'
|
|
3
|
+
|
|
4
|
+
describe IOStruct do
|
|
5
|
+
describe ".get_type_size" do
|
|
6
|
+
it "returns size for known types" do
|
|
7
|
+
expect(described_class.get_type_size('int')).to eq 4
|
|
8
|
+
expect(described_class.get_type_size('char')).to eq 1
|
|
9
|
+
expect(described_class.get_type_size('short')).to eq 2
|
|
10
|
+
expect(described_class.get_type_size('long long')).to eq 8
|
|
11
|
+
expect(described_class.get_type_size('double')).to eq 8
|
|
12
|
+
expect(described_class.get_type_size('float')).to eq 4
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "works with symbol input" do
|
|
16
|
+
expect(described_class.get_type_size(:int)).to eq 4
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "returns nil for unknown types" do
|
|
20
|
+
expect(described_class.get_type_size('unknown')).to be_nil
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe "hash format ctor" do
|
|
25
|
+
it "works" do
|
|
26
|
+
klass = described_class.new(
|
|
27
|
+
fields: {
|
|
28
|
+
x: "int",
|
|
29
|
+
y: :int,
|
|
30
|
+
z: { type: :int },
|
|
31
|
+
},
|
|
32
|
+
struct_name: 'Point'
|
|
33
|
+
)
|
|
34
|
+
expect(klass.new.inspect).to match(/<Point x=nil y=nil z=nil>/)
|
|
35
|
+
expect(klass.size).to eq(12)
|
|
36
|
+
expect(klass::SIZE).to eq(12)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "respects :name" do
|
|
40
|
+
klass = described_class.new(
|
|
41
|
+
fields: {
|
|
42
|
+
x: "int",
|
|
43
|
+
y: :int,
|
|
44
|
+
z: { type: :int },
|
|
45
|
+
},
|
|
46
|
+
struct_name: 'Point'
|
|
47
|
+
)
|
|
48
|
+
expect(klass.new.inspect).to match(/<Point x=nil y=nil z=nil>/)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "respects :offset" do
|
|
52
|
+
klass = described_class.new(
|
|
53
|
+
struct_name: 'Point',
|
|
54
|
+
fields: {
|
|
55
|
+
x: "int",
|
|
56
|
+
y: :int,
|
|
57
|
+
z: { type: :int, offset: 0x10 },
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
expect(klass.new.inspect).to match(/<Point x=nil y=nil z=nil>/)
|
|
61
|
+
expect(klass.size).to eq(0x14)
|
|
62
|
+
|
|
63
|
+
obj = klass.read((0..0x20).to_a.pack('i*'))
|
|
64
|
+
expect(obj.x).to eq(0)
|
|
65
|
+
expect(obj.y).to eq(1)
|
|
66
|
+
expect(obj.z).to eq(4)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context 'when two fields have same offset' do
|
|
70
|
+
it 'fails' do
|
|
71
|
+
expect do
|
|
72
|
+
described_class.new(
|
|
73
|
+
struct_name: 'Point',
|
|
74
|
+
fields: {
|
|
75
|
+
x: { type: :int, offset: 0 },
|
|
76
|
+
y: { type: :char, offset: 0 },
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
end.to raise_error(RuntimeError)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "can override size" do
|
|
84
|
+
klass = described_class.new(
|
|
85
|
+
fields: {
|
|
86
|
+
x: "int",
|
|
87
|
+
y: :int,
|
|
88
|
+
z: { type: 'int' },
|
|
89
|
+
},
|
|
90
|
+
size: 0x100
|
|
91
|
+
)
|
|
92
|
+
expect(klass.size).to eq(0x100)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "supports arrays" do
|
|
96
|
+
klass = described_class.new(
|
|
97
|
+
fields: {
|
|
98
|
+
x: "int",
|
|
99
|
+
a: { type: 'int', count: 3 },
|
|
100
|
+
y: :int,
|
|
101
|
+
},
|
|
102
|
+
)
|
|
103
|
+
expect(klass.size).to eq(4 * 5)
|
|
104
|
+
|
|
105
|
+
v = klass.read([1, 2, 3, 4, 5, 6, 7].pack('i*'))
|
|
106
|
+
|
|
107
|
+
expect(v.x).to eq(1)
|
|
108
|
+
expect(v.a).to eq([2, 3, 4])
|
|
109
|
+
expect(v.y).to eq(5)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it "supports nesting" do
|
|
113
|
+
point = described_class.new( fields: { x: "int", y: :int } )
|
|
114
|
+
rect = described_class.new(
|
|
115
|
+
struct_name: 'Rect',
|
|
116
|
+
fields: {
|
|
117
|
+
topLeft: point,
|
|
118
|
+
bottomRight: point,
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
expect(rect.size).to eq(16)
|
|
122
|
+
|
|
123
|
+
r = rect.read([10, 20, 100, 200].pack('i*'))
|
|
124
|
+
expect(r.topLeft).to be_instance_of(point)
|
|
125
|
+
expect(r.bottomRight).to be_instance_of(point)
|
|
126
|
+
expect(r.topLeft.x).to eq(10)
|
|
127
|
+
expect(r.topLeft.y).to eq(20)
|
|
128
|
+
expect(r.bottomRight.x).to eq(100)
|
|
129
|
+
expect(r.bottomRight.y).to eq(200)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it "packs nested structs" do
|
|
133
|
+
point = described_class.new(fields: { x: "int", y: :int })
|
|
134
|
+
rect = described_class.new(fields: { topLeft: point, bottomRight: point })
|
|
135
|
+
|
|
136
|
+
r = rect.read([10, 20, 100, 200].pack('i*'))
|
|
137
|
+
packed = r.pack
|
|
138
|
+
reparsed = rect.read(packed)
|
|
139
|
+
|
|
140
|
+
expect(reparsed.topLeft.x).to eq 10
|
|
141
|
+
expect(reparsed.topLeft.y).to eq 20
|
|
142
|
+
expect(reparsed.bottomRight.x).to eq 100
|
|
143
|
+
expect(reparsed.bottomRight.y).to eq 200
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
it "supports nested struct arrays" do
|
|
147
|
+
point = described_class.new(fields: { x: "int", y: :int })
|
|
148
|
+
polygon = described_class.new(
|
|
149
|
+
fields: {
|
|
150
|
+
num_points: 'int',
|
|
151
|
+
points: { type: point, count: 3 },
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
expect(polygon.size).to eq(4 + (8 * 3))
|
|
155
|
+
|
|
156
|
+
data = [3, 10, 20, 30, 40, 50, 60].pack('i*')
|
|
157
|
+
p = polygon.read(data)
|
|
158
|
+
|
|
159
|
+
expect(p.num_points).to eq 3
|
|
160
|
+
expect(p.points.size).to eq 3
|
|
161
|
+
expect(p.points[0]).to be_instance_of(point)
|
|
162
|
+
expect(p.points[0].x).to eq 10
|
|
163
|
+
expect(p.points[0].y).to eq 20
|
|
164
|
+
expect(p.points[1].x).to eq 30
|
|
165
|
+
expect(p.points[1].y).to eq 40
|
|
166
|
+
expect(p.points[2].x).to eq 50
|
|
167
|
+
expect(p.points[2].y).to eq 60
|
|
168
|
+
|
|
169
|
+
# Test round-trip
|
|
170
|
+
reparsed = polygon.read(p.pack)
|
|
171
|
+
expect(reparsed.points[2].y).to eq 60
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it "packs arrays" do
|
|
175
|
+
klass = described_class.new(
|
|
176
|
+
fields: {
|
|
177
|
+
x: "int",
|
|
178
|
+
a: { type: 'int', count: 3 },
|
|
179
|
+
y: :int,
|
|
180
|
+
}
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
v = klass.read([1, 2, 3, 4, 5].pack('i*'))
|
|
184
|
+
packed = v.pack
|
|
185
|
+
reparsed = klass.read(packed)
|
|
186
|
+
|
|
187
|
+
expect(reparsed.x).to eq 1
|
|
188
|
+
expect(reparsed.a).to eq [2, 3, 4]
|
|
189
|
+
expect(reparsed.y).to eq 5
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
context "error handling" do
|
|
193
|
+
it "raises on unknown field type" do
|
|
194
|
+
expect do
|
|
195
|
+
described_class.new(fields: { x: "unknown_type" })
|
|
196
|
+
end.to raise_error(/unknown field type/)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
it "raises on invalid type format" do
|
|
200
|
+
expect do
|
|
201
|
+
described_class.new(fields: { x: 12345 })
|
|
202
|
+
end.to raise_error(/unexpected field desc type/)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
it "raises when forced size is smaller than actual" do
|
|
206
|
+
expect do
|
|
207
|
+
described_class.new(
|
|
208
|
+
fields: { x: "int", y: "int", z: "int" },
|
|
209
|
+
size: 4
|
|
210
|
+
)
|
|
211
|
+
end.to raise_error(/actual struct size .* is greater than forced size/)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
context "C type aliases" do
|
|
216
|
+
it "supports uint types" do
|
|
217
|
+
klass = described_class.new(fields: {
|
|
218
|
+
a: 'uint8_t',
|
|
219
|
+
b: 'uint16_t',
|
|
220
|
+
c: 'uint32_t',
|
|
221
|
+
d: 'uint64_t'
|
|
222
|
+
})
|
|
223
|
+
expect(klass.size).to eq(1 + 2 + 4 + 8)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
it "supports int types" do
|
|
227
|
+
klass = described_class.new(fields: {
|
|
228
|
+
a: 'int8_t',
|
|
229
|
+
b: 'int16_t',
|
|
230
|
+
c: 'int32_t',
|
|
231
|
+
d: 'int64_t'
|
|
232
|
+
})
|
|
233
|
+
expect(klass.size).to eq(1 + 2 + 4 + 8)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
it "supports _BYTE type" do
|
|
237
|
+
klass = described_class.new(fields: { a: '_BYTE' })
|
|
238
|
+
expect(klass.size).to eq 1
|
|
239
|
+
expect(klass.read("\xff").a).to eq 255 # unsigned
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
context "endian types" do
|
|
244
|
+
it "supports big-endian uint16" do
|
|
245
|
+
klass = described_class.new(fields: { a: 'uint16_be' })
|
|
246
|
+
expect(klass.size).to eq 2
|
|
247
|
+
expect(klass.read("\x01\x02").a).to eq 0x0102 # big-endian
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
it "supports big-endian uint32" do
|
|
251
|
+
klass = described_class.new(fields: { a: 'uint32_be' })
|
|
252
|
+
expect(klass.size).to eq 4
|
|
253
|
+
expect(klass.read("\x01\x02\x03\x04").a).to eq 0x01020304
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
it "supports little-endian uint16" do
|
|
257
|
+
klass = described_class.new(fields: { a: 'uint16_le' })
|
|
258
|
+
expect(klass.size).to eq 2
|
|
259
|
+
expect(klass.read("\x01\x02").a).to eq 0x0201 # little-endian
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
it "supports little-endian uint32" do
|
|
263
|
+
klass = described_class.new(fields: { a: 'uint32_le' })
|
|
264
|
+
expect(klass.size).to eq 4
|
|
265
|
+
expect(klass.read("\x01\x02\x03\x04").a).to eq 0x04030201
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
it "supports alternate names (be16, le32, etc.)" do
|
|
269
|
+
klass = described_class.new(fields: {
|
|
270
|
+
a: 'be16',
|
|
271
|
+
b: 'be32',
|
|
272
|
+
c: 'le16',
|
|
273
|
+
d: 'le32'
|
|
274
|
+
})
|
|
275
|
+
expect(klass.size).to eq(2 + 4 + 2 + 4)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
it "round-trips big-endian values" do
|
|
279
|
+
klass = described_class.new(fields: { a: 'uint16_be', b: 'uint32_be' })
|
|
280
|
+
obj = klass.read([0x1234, 0xDEADBEEF].pack('nN'))
|
|
281
|
+
expect(obj.a).to eq 0x1234
|
|
282
|
+
expect(obj.b).to eq 0xDEADBEEF
|
|
283
|
+
expect(obj.pack).to eq [0x1234, 0xDEADBEEF].pack('nN')
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
end
|
data/spec/iostruct_spec.rb
CHANGED
|
@@ -2,40 +2,240 @@ require 'spec_helper'
|
|
|
2
2
|
require 'stringio'
|
|
3
3
|
|
|
4
4
|
describe IOStruct do
|
|
5
|
-
describe "
|
|
5
|
+
describe "#pack" do
|
|
6
|
+
it "serializes struct back to binary" do
|
|
7
|
+
struct = described_class.new('L S C', :a, :b, :c)
|
|
8
|
+
obj = struct.new(a: 0x12345678, b: 0xABCD, c: 0xEF)
|
|
9
|
+
expect(obj.pack).to eq [0x12345678, 0xABCD, 0xEF].pack('L S C')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "round-trips correctly" do
|
|
13
|
+
struct = described_class.new('L S C', :a, :b, :c)
|
|
14
|
+
data = [123, 456, 78].pack('L S C')
|
|
15
|
+
obj = struct.read(data)
|
|
16
|
+
expect(obj.pack).to eq data
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe "#empty?" do
|
|
21
|
+
let(:struct) { described_class.new('L S', :a, :b) }
|
|
22
|
+
|
|
23
|
+
it "returns true when all fields are zero" do
|
|
24
|
+
expect(struct.new(a: 0, b: 0)).to be_empty
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "returns true when all fields are nil" do
|
|
28
|
+
expect(struct.new).to be_empty
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "returns false when any field is non-zero" do
|
|
32
|
+
expect(struct.new(a: 1, b: 0)).not_to be_empty
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "returns true for null-filled strings" do
|
|
36
|
+
str_struct = described_class.new('a4', :name)
|
|
37
|
+
expect(str_struct.new(name: "\x00\x00\x00\x00")).to be_empty
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "returns false for non-empty strings" do
|
|
41
|
+
str_struct = described_class.new('a4', :name)
|
|
42
|
+
expect(str_struct.new(name: "test")).not_to be_empty
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe "#inspect" do
|
|
47
|
+
[nil, "MyClass"].each do |struct_name|
|
|
48
|
+
context "when struct_name is #{struct_name.inspect}" do
|
|
49
|
+
[:hex, :dec].each do |inspect_mode|
|
|
50
|
+
context "when inspect is :#{inspect_mode}" do
|
|
51
|
+
context "for IOStruct" do
|
|
52
|
+
it "shows default struct name" do
|
|
53
|
+
struct = described_class.new('L S C', :a, :b, :c, inspect: inspect_mode, struct_name: struct_name)
|
|
54
|
+
cname = struct_name || "struct"
|
|
55
|
+
expect(struct.new.inspect).to match(/<#{cname} a=/)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
context "for IOStruct anonymous subclass" do
|
|
60
|
+
it "shows default struct name" do
|
|
61
|
+
struct = Class.new( described_class.new('L S C', :a, :b, :c, inspect: inspect_mode, struct_name: struct_name) )
|
|
62
|
+
cname = struct_name || "struct"
|
|
63
|
+
expect(struct.new.inspect).to match(/<#{cname} a=/)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
context "for named IOStruct subclass" do
|
|
68
|
+
it "shows custom struct name" do
|
|
69
|
+
stub_const("C1", described_class.new('L S C', :a, :b, :c, inspect: inspect_mode, struct_name: struct_name) )
|
|
70
|
+
cname = struct_name || "C1"
|
|
71
|
+
expect(C1.new.inspect).to match(/<#{cname} a=/)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
describe "hash-style initialization" do
|
|
81
|
+
let(:struct) { described_class.new('L S C', :a, :b, :c) }
|
|
82
|
+
|
|
83
|
+
it "initializes fields by name" do
|
|
84
|
+
obj = struct.new(a: 100, c: 50)
|
|
85
|
+
expect(obj.a).to eq 100
|
|
86
|
+
expect(obj.b).to be_nil
|
|
87
|
+
expect(obj.c).to eq 50
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
describe "auto-generated field names" do
|
|
92
|
+
it "generates names based on offset" do
|
|
93
|
+
struct = described_class.new('C S L')
|
|
94
|
+
expect(struct.members).to eq [:f0, :f1, :f3]
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
describe "field renaming" do
|
|
99
|
+
it "renames auto-generated fields via kwargs" do
|
|
100
|
+
struct = described_class.new('C S L', f0: :byte_field, f3: :long_field)
|
|
101
|
+
expect(struct.members).to eq [:byte_field, :f1, :long_field]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "renames named fields via kwargs" do
|
|
105
|
+
struct = described_class.new('C S L', :a, :b, :c, a: :first, c: :last)
|
|
106
|
+
expect(struct.members).to eq [:first, :b, :last]
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
describe "float types" do
|
|
111
|
+
it "unpacks single-precision float (f/F)" do
|
|
112
|
+
data = [3.14159].pack('f')
|
|
113
|
+
struct = described_class.new('f', :val)
|
|
114
|
+
expect(struct.read(data).val).to be_within(0.0001).of(3.14159)
|
|
115
|
+
expect(struct::SIZE).to eq 4
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it "unpacks double-precision float (d/D)" do
|
|
119
|
+
data = [3.141592653589793].pack('d')
|
|
120
|
+
struct = described_class.new('d', :val)
|
|
121
|
+
expect(struct.read(data).val).to be_within(0.0000001).of(3.141592653589793)
|
|
122
|
+
expect(struct::SIZE).to eq 8
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "unpacks little-endian floats (e/E)" do
|
|
126
|
+
data = [2.5].pack('e')
|
|
127
|
+
struct = described_class.new('e', :val)
|
|
128
|
+
expect(struct.read(data).val).to be_within(0.0001).of(2.5)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it "unpacks big-endian floats (g/G)" do
|
|
132
|
+
data = [2.5].pack('G')
|
|
133
|
+
struct = described_class.new('G', :val)
|
|
134
|
+
expect(struct.read(data).val).to be_within(0.0001).of(2.5)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it "formats floats in to_table" do
|
|
138
|
+
struct = described_class.new('f', :val)
|
|
139
|
+
obj = struct.new(val: 3.14159)
|
|
140
|
+
expect(obj.to_table).to match(/val=\s*3\.142/)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
describe "signed integer types" do
|
|
145
|
+
it "reads signed 8-bit (c)" do
|
|
146
|
+
struct = described_class.new('c', :val)
|
|
147
|
+
expect(struct.read("\x80").val).to eq(-128)
|
|
148
|
+
expect(struct.read("\x7f").val).to eq(127)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it "reads signed 16-bit (s)" do
|
|
152
|
+
struct = described_class.new('s', :val)
|
|
153
|
+
expect(struct.read("\x00\x80").val).to eq(-32768)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
it "reads signed 32-bit (i)" do
|
|
157
|
+
struct = described_class.new('i', :val)
|
|
158
|
+
expect(struct.read("\x00\x00\x00\x80").val).to eq(-2147483648)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it "reads signed 64-bit (q)" do
|
|
162
|
+
struct = described_class.new('q', :val)
|
|
163
|
+
expect(struct.read("\x00\x00\x00\x00\x00\x00\x00\x80").val).to eq(-9223372036854775808)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
describe "string types" do
|
|
168
|
+
it "unpacks space-padded string (A)" do
|
|
169
|
+
struct = described_class.new('A8', :name)
|
|
170
|
+
obj = struct.read("hello ")
|
|
171
|
+
expect(obj.name).to eq "hello"
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it "unpacks null-terminated string (Z)" do
|
|
175
|
+
struct = described_class.new('Z8', :name)
|
|
176
|
+
obj = struct.read("hello\x00\x00\x00")
|
|
177
|
+
expect(obj.name).to eq "hello"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
it "unpacks raw string (a)" do
|
|
181
|
+
struct = described_class.new('a8', :name)
|
|
182
|
+
obj = struct.read("hello\x00\x00\x00")
|
|
183
|
+
expect(obj.name).to eq "hello\x00\x00\x00"
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
describe "error handling" do
|
|
188
|
+
it "raises when no fmt and no :fields" do
|
|
189
|
+
expect { described_class.new }.to raise_error(/no fmt and no :fields/)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
it "raises when reading from unsupported source" do
|
|
193
|
+
struct = described_class.new('L', :val)
|
|
194
|
+
expect { struct.read(12345) }.to raise_error(/don't know how to read/)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
describe "inspect_name_override (deprecated)" do
|
|
199
|
+
it "works as alias for struct_name" do
|
|
200
|
+
struct = described_class.new('L', :val, inspect_name_override: 'MyStruct')
|
|
201
|
+
expect(struct.new.inspect).to match(/<MyStruct/)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
describe ":name" do
|
|
6
206
|
context "when set" do
|
|
7
207
|
it "uses the custom name" do
|
|
8
|
-
x =
|
|
9
|
-
expect(x.new.inspect).to match
|
|
208
|
+
x = described_class.new('LL', :x, :y, struct_name: 'Point')
|
|
209
|
+
expect(x.new.inspect).to match(/<Point x=nil y=nil>/)
|
|
10
210
|
end
|
|
11
211
|
end
|
|
12
212
|
|
|
13
213
|
context "when not set" do
|
|
14
214
|
it "has default name" do
|
|
15
|
-
x =
|
|
16
|
-
expect(x.new.inspect).to match
|
|
215
|
+
x = described_class.new('LL', :x, :y)
|
|
216
|
+
expect(x.new.inspect).to match(/<struct x=nil y=nil>/)
|
|
17
217
|
end
|
|
18
218
|
end
|
|
19
219
|
end
|
|
20
220
|
|
|
21
|
-
describe "
|
|
221
|
+
describe "read" do
|
|
22
222
|
let(:a) { [12345, 56789] }
|
|
23
223
|
let(:data) { a.pack('L2') }
|
|
24
224
|
|
|
25
225
|
it "reads from IO" do
|
|
26
|
-
x =
|
|
226
|
+
x = described_class.new('LL', :x, :y).read(StringIO.new(data))
|
|
27
227
|
expect(x.x).to eq a[0]
|
|
28
228
|
expect(x.y).to eq a[1]
|
|
29
229
|
end
|
|
30
230
|
|
|
31
231
|
it "reads from String" do
|
|
32
|
-
x =
|
|
232
|
+
x = described_class.new('LL', :x, :y).read(data)
|
|
33
233
|
expect(x.x).to eq a[0]
|
|
34
234
|
expect(x.y).to eq a[1]
|
|
35
235
|
end
|
|
36
236
|
|
|
37
237
|
it "creates a new instance of a subclass" do
|
|
38
|
-
klass = Class.new(
|
|
238
|
+
klass = Class.new( described_class.new('LL', :x, :y) )
|
|
39
239
|
x = klass.read(data)
|
|
40
240
|
expect(x).to be_a klass
|
|
41
241
|
end
|
|
@@ -43,7 +243,7 @@ describe IOStruct do
|
|
|
43
243
|
|
|
44
244
|
context "zero-length strings" do
|
|
45
245
|
let(:data) { [1, 2].pack('CC') }
|
|
46
|
-
let(:struct) {
|
|
246
|
+
let(:struct) { described_class.new('C a0 C', :a, :b, :c) }
|
|
47
247
|
|
|
48
248
|
it "deserializes" do
|
|
49
249
|
x = struct.read(data)
|
|
@@ -61,8 +261,8 @@ describe IOStruct do
|
|
|
61
261
|
end
|
|
62
262
|
|
|
63
263
|
it "reads correct number of bytes from IO" do
|
|
64
|
-
io = StringIO.new(data*2)
|
|
65
|
-
|
|
264
|
+
io = StringIO.new(data * 2)
|
|
265
|
+
struct.read(io)
|
|
66
266
|
expect(io.pos).to eq 2
|
|
67
267
|
end
|
|
68
268
|
|
|
@@ -75,29 +275,30 @@ describe IOStruct do
|
|
|
75
275
|
it "skips on 'x'" do
|
|
76
276
|
a = [12345, 56789]
|
|
77
277
|
data = a.pack('L2')
|
|
78
|
-
x =
|
|
278
|
+
x = described_class.new('x4L', :y).read(data)
|
|
79
279
|
expect(x.y).to eq a[1]
|
|
80
280
|
end
|
|
81
281
|
|
|
82
282
|
it "unpacks hex-string (H)" do
|
|
83
283
|
data = "1234"
|
|
84
|
-
struct =
|
|
284
|
+
struct = described_class.new('H8', :x).read(data)
|
|
85
285
|
expect(struct.x).to eq "31323334"
|
|
86
286
|
expect(struct.pack).to eq data
|
|
87
287
|
end
|
|
88
288
|
|
|
89
289
|
it "unpacks reverse-nibbled hex-string (h)" do
|
|
90
290
|
data = "1234"
|
|
91
|
-
struct =
|
|
291
|
+
struct = described_class.new('h8', :x).read(data)
|
|
92
292
|
expect(struct.x).to eq "13233343"
|
|
93
293
|
expect(struct.pack).to eq data
|
|
94
294
|
end
|
|
95
295
|
|
|
296
|
+
# rubocop:disable RSpec/RepeatedExample
|
|
96
297
|
['n', 'N', 'S>', 'L>', 'I>'].each do |fmt|
|
|
97
298
|
it "unpacks unsigned big-endian '#{fmt}'" do
|
|
98
299
|
a = [12345]
|
|
99
300
|
data = a.pack(fmt)
|
|
100
|
-
x =
|
|
301
|
+
x = described_class.new(fmt, :x).read(data)
|
|
101
302
|
expect(x.x).to eq a[0]
|
|
102
303
|
expect(x.pack).to eq data
|
|
103
304
|
end
|
|
@@ -107,27 +308,26 @@ describe IOStruct do
|
|
|
107
308
|
it "unpacks unsigned little-endian '#{fmt}'" do
|
|
108
309
|
a = [12345]
|
|
109
310
|
data = a.pack(fmt)
|
|
110
|
-
x =
|
|
311
|
+
x = described_class.new(fmt, :x).read(data)
|
|
111
312
|
expect(x.x).to eq a[0]
|
|
112
313
|
expect(x.pack).to eq data
|
|
113
314
|
end
|
|
114
315
|
end
|
|
316
|
+
# rubocop:enable RSpec/RepeatedExample
|
|
115
317
|
|
|
116
318
|
it "throws exception on unknown format" do
|
|
117
|
-
expect {
|
|
319
|
+
expect { described_class.new('K', :x) }.to raise_error('Unknown field type "K"')
|
|
118
320
|
end
|
|
119
321
|
|
|
120
322
|
context '__offset field' do
|
|
121
323
|
let(:data) { 0x100.times.to_a.pack('L*') }
|
|
122
324
|
let(:io) { StringIO.new(data) }
|
|
123
|
-
let(:struct) {
|
|
325
|
+
let(:struct) { described_class.new('LLLL', :a, :b, :c, :d) }
|
|
124
326
|
|
|
125
327
|
context 'when src is an IO' do
|
|
126
328
|
it 'is set to the current IO position' do
|
|
127
329
|
a = []
|
|
128
|
-
|
|
129
|
-
a << struct.read(io)
|
|
130
|
-
end
|
|
330
|
+
a << struct.read(io) until io.eof?
|
|
131
331
|
expect(a.map(&:__offset)).to eq (0...0x400).step(0x10).to_a
|
|
132
332
|
end
|
|
133
333
|
end
|
|
@@ -139,4 +339,77 @@ describe IOStruct do
|
|
|
139
339
|
end
|
|
140
340
|
end
|
|
141
341
|
end
|
|
342
|
+
|
|
343
|
+
describe "to_table" do
|
|
344
|
+
context "when inspect is :hex" do
|
|
345
|
+
it "formats signed struct as table" do
|
|
346
|
+
signed_struct = described_class.new('c s i q', inspect: :hex)
|
|
347
|
+
expect(signed_struct.new.to_table).to eq(
|
|
348
|
+
"<struct f0= 0 f1= 0 f3= 0 f7= 0>"
|
|
349
|
+
)
|
|
350
|
+
expect(signed_struct.read("\xff" * 16).to_table).to eq(
|
|
351
|
+
"<struct f0=ff f1=ffff f3=ffffffff f7=ffffffffffffffff>"
|
|
352
|
+
)
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
it "formats unsigned struct as table" do
|
|
356
|
+
unsigned_struct = described_class.new('C S I Q', inspect: :hex)
|
|
357
|
+
expect(unsigned_struct.new.to_table).to eq(
|
|
358
|
+
"<struct f0= 0 f1= 0 f3= 0 f7= 0>"
|
|
359
|
+
)
|
|
360
|
+
expect(unsigned_struct.read("\xff" * 16).to_table).to eq(
|
|
361
|
+
"<struct f0=ff f1=ffff f3=ffffffff f7=ffffffffffffffff>"
|
|
362
|
+
)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
it "works with unknown types" do
|
|
366
|
+
struct = described_class.new('C', :a, :name, inspect: :hex)
|
|
367
|
+
s = struct.new
|
|
368
|
+
expect(s.to_table).to eq(
|
|
369
|
+
"<struct a= 0 name=nil>"
|
|
370
|
+
)
|
|
371
|
+
s.name = "test"
|
|
372
|
+
expect(s.to_table).to eq(
|
|
373
|
+
'<struct a= 0 name="test">'
|
|
374
|
+
)
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
context "when inspect is not :hex" do
|
|
379
|
+
it "formats signed struct as table" do
|
|
380
|
+
signed_struct = described_class.new('c s i q', inspect: :dec)
|
|
381
|
+
expect(signed_struct.new.to_table).to eq(
|
|
382
|
+
"<struct f0= 0 f1= 0 f3= 0 f7= 0>"
|
|
383
|
+
)
|
|
384
|
+
expect(signed_struct.read("\xff" * 16).to_table).to eq(
|
|
385
|
+
"<struct f0= -1 f1= -1 f3= -1 f7= -1>"
|
|
386
|
+
)
|
|
387
|
+
expect(signed_struct.read("\x80\x00\x80\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80").to_table).to eq(
|
|
388
|
+
"<struct f0=-128 f1=-32768 f3=-2147483648 f7=-9223372036854775808>"
|
|
389
|
+
)
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
it "formats unsigned struct as table" do
|
|
393
|
+
unsigned_struct = described_class.new('C S I Q', inspect: :dec)
|
|
394
|
+
expect(unsigned_struct.new.to_table).to eq(
|
|
395
|
+
"<struct f0= 0 f1= 0 f3= 0 f7= 0>"
|
|
396
|
+
)
|
|
397
|
+
expect(unsigned_struct.read("\xff" * 16).to_table).to eq(
|
|
398
|
+
"<struct f0= 255 f1= 65535 f3= 4294967295 f7=18446744073709551615>"
|
|
399
|
+
)
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
it "works with unknown types" do
|
|
403
|
+
struct = described_class.new('C', :a, :name, inspect: :dec)
|
|
404
|
+
s = struct.new
|
|
405
|
+
expect(s.to_table).to eq(
|
|
406
|
+
"<struct a= 0 name=nil>"
|
|
407
|
+
)
|
|
408
|
+
s.name = "test"
|
|
409
|
+
expect(s.to_table).to eq(
|
|
410
|
+
'<struct a= 0 name="test">'
|
|
411
|
+
)
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
end
|
|
142
415
|
end
|