iostruct 0.5.0 → 0.6.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 +68 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +35 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +49 -9
- data/README.md +247 -6
- data/Rakefile +3 -1
- data/iostruct.gemspec +8 -9
- data/lib/iostruct/hash_fmt.rb +92 -0
- data/lib/iostruct/pack_fmt.rb +68 -0
- data/lib/iostruct/version.rb +1 -1
- data/lib/iostruct.rb +122 -103
- data/spec/.rubocop.yml +27 -0
- data/spec/hash_fmt_spec.rb +215 -0
- data/spec/iostruct_spec.rb +260 -21
- metadata +12 -51
data/lib/iostruct/version.rb
CHANGED
data/lib/iostruct.rb
CHANGED
|
@@ -1,100 +1,63 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative 'iostruct/pack_fmt'
|
|
4
|
+
require_relative 'iostruct/hash_fmt'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
'A' => [1, String ], # arbitrary binary string (remove trailing nulls and ASCII spaces)
|
|
25
|
-
'a' => [1, String ], # arbitrary binary string
|
|
26
|
-
'Z' => [1, String ], # arbitrary binary string (remove trailing nulls)
|
|
27
|
-
'H' => [1, String ], # hex string (high nibble first)
|
|
28
|
-
'h' => [1, String ], # hex string (low nibble first)
|
|
29
|
-
|
|
30
|
-
'D' => [8, Float ], # double-precision, native format
|
|
31
|
-
'd' => [8, Float ],
|
|
32
|
-
'F' => [4, Float ], # single-precision, native format
|
|
33
|
-
'f' => [4, Float ],
|
|
34
|
-
'E' => [8, Float ], # double-precision, little-endian byte order
|
|
35
|
-
'e' => [4, Float ], # single-precision, little-endian byte order
|
|
36
|
-
'G' => [8, Float ], # double-precision, network (big-endian) byte order
|
|
37
|
-
'g' => [4, Float ], # single-precision, network (big-endian) byte order
|
|
38
|
-
|
|
39
|
-
'x' => [1, nil ], # skip forward one byte
|
|
40
|
-
}.freeze
|
|
41
|
-
|
|
42
|
-
FieldInfo = Struct.new :type, :size, :offset
|
|
43
|
-
|
|
44
|
-
def self.new fmt, *names, inspect: :hex, inspect_name_override: nil, **renames
|
|
45
|
-
fields, size = parse_format(fmt, names)
|
|
46
|
-
names = auto_names(fields, size) if names.empty?
|
|
47
|
-
names.map!{ |n| renames[n] || n } if renames.any?
|
|
48
|
-
|
|
49
|
-
Struct.new( *names ).tap do |x|
|
|
50
|
-
x.const_set 'FIELDS', names.zip(fields).to_h
|
|
51
|
-
x.const_set 'FORMAT', fmt
|
|
52
|
-
x.const_set 'SIZE', size
|
|
53
|
-
x.extend ClassMethods
|
|
54
|
-
x.include InstanceMethods
|
|
55
|
-
x.include HexInspect if inspect == :hex
|
|
56
|
-
x.define_singleton_method(:name) { inspect_name_override } if inspect_name_override
|
|
6
|
+
module IOStruct
|
|
7
|
+
extend PackFmt
|
|
8
|
+
extend HashFmt
|
|
9
|
+
|
|
10
|
+
# rubocop:disable Lint/StructNewOverride
|
|
11
|
+
FieldInfo = Struct.new :type, :size, :offset, :count, :fmt
|
|
12
|
+
# rubocop:enable Lint/StructNewOverride
|
|
13
|
+
|
|
14
|
+
def self.new fmt = nil, *names, inspect: :hex, inspect_name_override: nil, struct_name: nil, **kwargs
|
|
15
|
+
struct_name ||= inspect_name_override # XXX inspect_name_override is deprecated
|
|
16
|
+
if fmt
|
|
17
|
+
renames = kwargs
|
|
18
|
+
finfos, size = parse_pack_format(fmt, names)
|
|
19
|
+
names = auto_names(finfos, size) if names.empty?
|
|
20
|
+
names.map! { |n| renames[n] || n } if renames.any?
|
|
21
|
+
elsif kwargs[:fields]
|
|
22
|
+
fmt, names, finfos, size = parse_hash_format(name: struct_name, **kwargs)
|
|
23
|
+
else
|
|
24
|
+
raise "IOStruct: no fmt and no :fields specified"
|
|
57
25
|
end
|
|
58
|
-
end # self.new
|
|
59
26
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
offset += len/2
|
|
27
|
+
# if first argument to Struct.new() is a string - it creates a named struct in the Struct:: namespace
|
|
28
|
+
# convert all just for the case
|
|
29
|
+
names = names.map(&:to_sym)
|
|
30
|
+
|
|
31
|
+
Struct.new( *names ) do
|
|
32
|
+
const_set 'FIELDS', names.zip(finfos).to_h
|
|
33
|
+
const_set 'FORMAT', fmt
|
|
34
|
+
const_set 'SIZE', size
|
|
35
|
+
extend ClassMethods
|
|
36
|
+
include InstanceMethods
|
|
37
|
+
include NestedInstanceMethods if finfos.any?(&:fmt)
|
|
38
|
+
if inspect == :hex
|
|
39
|
+
include HexInspect
|
|
74
40
|
else
|
|
75
|
-
|
|
76
|
-
fields << FieldInfo.new(klass, size, offset)
|
|
77
|
-
offset += size
|
|
78
|
-
end
|
|
41
|
+
include DecInspect
|
|
79
42
|
end
|
|
43
|
+
define_singleton_method(:to_s) { struct_name } if struct_name
|
|
44
|
+
define_singleton_method(:name) { struct_name } if struct_name
|
|
80
45
|
end
|
|
81
|
-
|
|
82
|
-
end
|
|
46
|
+
end # self.new
|
|
83
47
|
|
|
84
|
-
def self.auto_names
|
|
48
|
+
def self.auto_names(fields, _size)
|
|
85
49
|
names = []
|
|
86
50
|
offset = 0
|
|
87
51
|
fields.each do |f|
|
|
88
52
|
names << sprintf("f%x", offset).to_sym
|
|
89
53
|
offset += f.size
|
|
90
54
|
end
|
|
91
|
-
#raise "size mismatch: #{size} != #{offset}" if size != offset
|
|
92
55
|
names
|
|
93
56
|
end
|
|
94
57
|
|
|
95
58
|
module ClassMethods
|
|
96
59
|
# src can be IO or String, or anything that responds to :read or :unpack
|
|
97
|
-
def read
|
|
60
|
+
def read(src, size = nil)
|
|
98
61
|
pos = nil
|
|
99
62
|
size ||= const_get 'SIZE'
|
|
100
63
|
data =
|
|
@@ -106,18 +69,15 @@ module IOStruct
|
|
|
106
69
|
else
|
|
107
70
|
raise "[?] don't know how to read from #{src.inspect}"
|
|
108
71
|
end
|
|
109
|
-
|
|
110
|
-
# $stderr.puts "[!] #{self.to_s} want #{size} bytes, got #{data.size}"
|
|
111
|
-
# end
|
|
112
|
-
new(*data.unpack(const_get('FORMAT'))).tap{ |x| x.__offset = pos }
|
|
72
|
+
new(*data.unpack(const_get('FORMAT'))).tap { |x| x.__offset = pos }
|
|
113
73
|
end
|
|
114
74
|
|
|
115
|
-
def
|
|
116
|
-
|
|
75
|
+
def name
|
|
76
|
+
'struct'
|
|
117
77
|
end
|
|
118
78
|
|
|
119
|
-
def
|
|
120
|
-
self
|
|
79
|
+
def size
|
|
80
|
+
self::SIZE
|
|
121
81
|
end
|
|
122
82
|
end # ClassMethods
|
|
123
83
|
|
|
@@ -129,7 +89,7 @@ module IOStruct
|
|
|
129
89
|
end
|
|
130
90
|
|
|
131
91
|
def empty?
|
|
132
|
-
to_a.all?{ |t| t == 0 || t.nil? || t.to_s.tr("\x00","").empty? }
|
|
92
|
+
to_a.all? { |t| t == 0 || t.nil? || t.to_s.tr("\x00", "").empty? }
|
|
133
93
|
end
|
|
134
94
|
|
|
135
95
|
# allow initializing individual struct members by name, like:
|
|
@@ -140,7 +100,7 @@ module IOStruct
|
|
|
140
100
|
def initialize *args
|
|
141
101
|
if args.size == 1 && args.first.is_a?(Hash)
|
|
142
102
|
super()
|
|
143
|
-
args.first.each do |k,v|
|
|
103
|
+
args.first.each do |k, v|
|
|
144
104
|
send "#{k}=", v
|
|
145
105
|
end
|
|
146
106
|
else
|
|
@@ -149,35 +109,94 @@ module IOStruct
|
|
|
149
109
|
end
|
|
150
110
|
end # InstanceMethods
|
|
151
111
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
112
|
+
# initialize nested structures / arrays
|
|
113
|
+
module NestedInstanceMethods
|
|
114
|
+
def initialize *args
|
|
115
|
+
super
|
|
116
|
+
self.class::FIELDS.each do |k, v|
|
|
117
|
+
next unless v.fmt
|
|
118
|
+
|
|
119
|
+
if (value = self[k])
|
|
120
|
+
self[k] = v.fmt.is_a?(String) ? value.unpack(v.fmt) : v.fmt.read(value)
|
|
159
121
|
end
|
|
160
|
-
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def pack
|
|
126
|
+
values = self.class::FIELDS.map do |k, v|
|
|
127
|
+
value = self[k]
|
|
128
|
+
next value unless v&.fmt && value
|
|
129
|
+
|
|
130
|
+
# Reverse the unpacking done in initialize
|
|
131
|
+
v.fmt.is_a?(String) ? value.pack(v.fmt) : value.pack
|
|
132
|
+
end
|
|
133
|
+
values.pack self.class.const_get('FORMAT')
|
|
161
134
|
end
|
|
135
|
+
end
|
|
162
136
|
|
|
137
|
+
module InspectBase
|
|
138
|
+
INT_MASKS = { 1 => 0xff, 2 => 0xffff, 4 => 0xffffffff, 8 => 0xffffffffffffffff }.freeze
|
|
139
|
+
|
|
140
|
+
# rubocop:disable Lint/DuplicateBranch
|
|
163
141
|
def to_table
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
142
|
+
values = to_a
|
|
143
|
+
"<#{self.class.name} " + self.class::FIELDS.map.with_index do |el, idx|
|
|
144
|
+
v = values[idx]
|
|
145
|
+
fname, f = el
|
|
146
|
+
|
|
147
|
+
"#{fname}=" +
|
|
148
|
+
case
|
|
149
|
+
when f.nil? # unknown field type
|
|
150
|
+
v.inspect
|
|
167
151
|
when f.type == Integer
|
|
168
|
-
"
|
|
152
|
+
v ||= 0 # avoid "`sprintf': can't convert nil into Integer" error
|
|
153
|
+
format_integer(v, f.size, fname)
|
|
169
154
|
when f.type == Float
|
|
170
|
-
"
|
|
155
|
+
v ||= 0 # avoid "`sprintf': can't convert nil into Float" error
|
|
156
|
+
"%8.3f" % v
|
|
171
157
|
else
|
|
172
|
-
|
|
158
|
+
v.inspect
|
|
173
159
|
end
|
|
174
|
-
"#{name}=#{fmt}"
|
|
175
160
|
end.join(' ') + ">"
|
|
176
|
-
|
|
161
|
+
end
|
|
162
|
+
# rubocop:enable Lint/DuplicateBranch
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
module DecInspect
|
|
166
|
+
include InspectBase
|
|
167
|
+
|
|
168
|
+
DEC_FMTS = { 1 => "%4d", 2 => "%6d", 4 => "%11d", 8 => "%20d" }.freeze
|
|
169
|
+
|
|
170
|
+
def format_integer(value, size, fname)
|
|
171
|
+
fmt = DEC_FMTS[size] || raise("Unsupported Integer size #{size} for field #{fname}")
|
|
172
|
+
fmt % value
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
module HexInspect
|
|
177
|
+
include InspectBase
|
|
178
|
+
|
|
179
|
+
HEX_FMTS = { 1 => "%2x", 2 => "%4x", 4 => "%8x", 8 => "%16x" }.freeze
|
|
180
|
+
|
|
181
|
+
def to_s
|
|
182
|
+
"<#{self.class.name} " + to_h.map do |k, v|
|
|
183
|
+
if v.is_a?(Integer) && v > 9
|
|
184
|
+
"#{k}=0x%x" % v
|
|
185
|
+
else
|
|
186
|
+
"#{k}=#{v.inspect}"
|
|
187
|
+
end
|
|
188
|
+
end.join(' ') + ">"
|
|
177
189
|
end
|
|
178
190
|
|
|
179
191
|
def inspect
|
|
180
192
|
to_s
|
|
181
193
|
end
|
|
194
|
+
|
|
195
|
+
# display as unsigned, because signed %x looks ugly: "..f" for -1
|
|
196
|
+
def format_integer(value, size, fname)
|
|
197
|
+
fmt = HEX_FMTS[size] || raise("Unsupported Integer size #{size} for field #{fname}")
|
|
198
|
+
mask = INT_MASKS[size] || raise("Unsupported Integer size #{size} for field #{fname}")
|
|
199
|
+
fmt % (value & mask)
|
|
200
|
+
end
|
|
182
201
|
end
|
|
183
202
|
end # IOStruct
|
data/spec/.rubocop.yml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
inherit_from: ../.rubocop.yml
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
Layout/SpaceInsideParens:
|
|
5
|
+
Enabled: false
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Style/FrozenStringLiteralComment:
|
|
9
|
+
Enabled: false
|
|
10
|
+
|
|
11
|
+
Style/SymbolArray:
|
|
12
|
+
Enabled: false
|
|
13
|
+
|
|
14
|
+
RSpec/ContextWording:
|
|
15
|
+
Enabled: false
|
|
16
|
+
|
|
17
|
+
RSpec/MultipleExpectations:
|
|
18
|
+
Enabled: false
|
|
19
|
+
|
|
20
|
+
RSpec/ExampleLength:
|
|
21
|
+
Enabled: false
|
|
22
|
+
|
|
23
|
+
RSpec/ExampleWording:
|
|
24
|
+
Enabled: false
|
|
25
|
+
|
|
26
|
+
RSpec/SpecFilePathFormat:
|
|
27
|
+
Enabled: false
|
|
@@ -0,0 +1,215 @@
|
|
|
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 "packs arrays" do
|
|
147
|
+
klass = described_class.new(
|
|
148
|
+
fields: {
|
|
149
|
+
x: "int",
|
|
150
|
+
a: { type: 'int', count: 3 },
|
|
151
|
+
y: :int,
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
v = klass.read([1, 2, 3, 4, 5].pack('i*'))
|
|
156
|
+
packed = v.pack
|
|
157
|
+
reparsed = klass.read(packed)
|
|
158
|
+
|
|
159
|
+
expect(reparsed.x).to eq 1
|
|
160
|
+
expect(reparsed.a).to eq [2, 3, 4]
|
|
161
|
+
expect(reparsed.y).to eq 5
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
context "error handling" do
|
|
165
|
+
it "raises on unknown field type" do
|
|
166
|
+
expect do
|
|
167
|
+
described_class.new(fields: { x: "unknown_type" })
|
|
168
|
+
end.to raise_error(/unknown field type/)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it "raises on invalid type format" do
|
|
172
|
+
expect do
|
|
173
|
+
described_class.new(fields: { x: 12345 })
|
|
174
|
+
end.to raise_error(/unexpected field desc type/)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
it "raises when forced size is smaller than actual" do
|
|
178
|
+
expect do
|
|
179
|
+
described_class.new(
|
|
180
|
+
fields: { x: "int", y: "int", z: "int" },
|
|
181
|
+
size: 4
|
|
182
|
+
)
|
|
183
|
+
end.to raise_error(/actual struct size .* is greater than forced size/)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
context "C type aliases" do
|
|
188
|
+
it "supports uint types" do
|
|
189
|
+
klass = described_class.new(fields: {
|
|
190
|
+
a: 'uint8_t',
|
|
191
|
+
b: 'uint16_t',
|
|
192
|
+
c: 'uint32_t',
|
|
193
|
+
d: 'uint64_t'
|
|
194
|
+
})
|
|
195
|
+
expect(klass.size).to eq(1 + 2 + 4 + 8)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
it "supports int types" do
|
|
199
|
+
klass = described_class.new(fields: {
|
|
200
|
+
a: 'int8_t',
|
|
201
|
+
b: 'int16_t',
|
|
202
|
+
c: 'int32_t',
|
|
203
|
+
d: 'int64_t'
|
|
204
|
+
})
|
|
205
|
+
expect(klass.size).to eq(1 + 2 + 4 + 8)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
it "supports _BYTE type" do
|
|
209
|
+
klass = described_class.new(fields: { a: '_BYTE' })
|
|
210
|
+
expect(klass.size).to eq 1
|
|
211
|
+
expect(klass.read("\xff").a).to eq 255 # unsigned
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|