unibuf 0.1.1 → 0.1.2
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_todo.yml +170 -200
- data/CODE_OF_CONDUCT.md +132 -0
- data/README.adoc +306 -114
- data/docs/CAPNPROTO.adoc +436 -0
- data/docs/FLATBUFFERS.adoc +430 -0
- data/docs/PROTOBUF.adoc +515 -0
- data/docs/TXTPROTO.adoc +369 -0
- data/lib/unibuf/commands/convert.rb +60 -2
- data/lib/unibuf/commands/schema.rb +68 -11
- data/lib/unibuf/errors.rb +23 -26
- data/lib/unibuf/models/capnproto/enum_definition.rb +72 -0
- data/lib/unibuf/models/capnproto/field_definition.rb +81 -0
- data/lib/unibuf/models/capnproto/interface_definition.rb +70 -0
- data/lib/unibuf/models/capnproto/method_definition.rb +81 -0
- data/lib/unibuf/models/capnproto/schema.rb +84 -0
- data/lib/unibuf/models/capnproto/struct_definition.rb +96 -0
- data/lib/unibuf/models/capnproto/union_definition.rb +62 -0
- data/lib/unibuf/models/flatbuffers/enum_definition.rb +69 -0
- data/lib/unibuf/models/flatbuffers/field_definition.rb +88 -0
- data/lib/unibuf/models/flatbuffers/schema.rb +102 -0
- data/lib/unibuf/models/flatbuffers/struct_definition.rb +70 -0
- data/lib/unibuf/models/flatbuffers/table_definition.rb +73 -0
- data/lib/unibuf/models/flatbuffers/union_definition.rb +60 -0
- data/lib/unibuf/models/message.rb +10 -0
- data/lib/unibuf/parsers/capnproto/binary_parser.rb +267 -0
- data/lib/unibuf/parsers/capnproto/grammar.rb +272 -0
- data/lib/unibuf/parsers/capnproto/list_reader.rb +208 -0
- data/lib/unibuf/parsers/capnproto/pointer_decoder.rb +163 -0
- data/lib/unibuf/parsers/capnproto/processor.rb +348 -0
- data/lib/unibuf/parsers/capnproto/segment_reader.rb +131 -0
- data/lib/unibuf/parsers/capnproto/struct_reader.rb +199 -0
- data/lib/unibuf/parsers/flatbuffers/binary_parser.rb +325 -0
- data/lib/unibuf/parsers/flatbuffers/grammar.rb +235 -0
- data/lib/unibuf/parsers/flatbuffers/processor.rb +299 -0
- data/lib/unibuf/serializers/binary_serializer.rb +218 -0
- data/lib/unibuf/serializers/capnproto/binary_serializer.rb +402 -0
- data/lib/unibuf/serializers/capnproto/list_writer.rb +199 -0
- data/lib/unibuf/serializers/capnproto/pointer_encoder.rb +118 -0
- data/lib/unibuf/serializers/capnproto/segment_builder.rb +124 -0
- data/lib/unibuf/serializers/capnproto/struct_writer.rb +139 -0
- data/lib/unibuf/serializers/flatbuffers/binary_serializer.rb +167 -0
- data/lib/unibuf/version.rb +1 -1
- data/lib/unibuf.rb +27 -0
- metadata +36 -1
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../models/capnproto/schema"
|
|
4
|
+
require_relative "../../models/capnproto/struct_definition"
|
|
5
|
+
require_relative "../../models/capnproto/field_definition"
|
|
6
|
+
require_relative "../../models/capnproto/enum_definition"
|
|
7
|
+
require_relative "../../models/capnproto/interface_definition"
|
|
8
|
+
require_relative "../../models/capnproto/method_definition"
|
|
9
|
+
require_relative "../../models/capnproto/union_definition"
|
|
10
|
+
|
|
11
|
+
module Unibuf
|
|
12
|
+
module Parsers
|
|
13
|
+
module Capnproto
|
|
14
|
+
# Processor to transform Cap'n Proto AST to Schema models
|
|
15
|
+
class Processor
|
|
16
|
+
class << self
|
|
17
|
+
def process(ast)
|
|
18
|
+
return Models::Capnproto::Schema.new unless ast
|
|
19
|
+
|
|
20
|
+
elements = Array(ast)
|
|
21
|
+
|
|
22
|
+
attributes = {
|
|
23
|
+
file_id: extract_file_id(elements),
|
|
24
|
+
usings: extract_usings(elements),
|
|
25
|
+
structs: extract_structs(elements),
|
|
26
|
+
enums: extract_enums(elements),
|
|
27
|
+
interfaces: extract_interfaces(elements),
|
|
28
|
+
constants: extract_constants(elements),
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
Models::Capnproto::Schema.new(attributes)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def extract_file_id(elements)
|
|
37
|
+
file_id_element = elements.find { |el| el.key?(:file_id) }
|
|
38
|
+
return nil unless file_id_element
|
|
39
|
+
|
|
40
|
+
file_id_element[:file_id][:number].to_s
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def extract_usings(elements)
|
|
44
|
+
elements.select { |el| el.key?(:using) }.map do |el|
|
|
45
|
+
{
|
|
46
|
+
alias: el[:using][:alias][:identifier].to_s,
|
|
47
|
+
import_path: el[:using][:import_path][:string].to_s,
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def extract_structs(elements)
|
|
53
|
+
elements.select { |el| el.key?(:struct) }.map do |el|
|
|
54
|
+
process_struct(el[:struct])
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def extract_enums(elements)
|
|
59
|
+
elements.select { |el| el.key?(:enum) }.map do |el|
|
|
60
|
+
process_enum(el[:enum])
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def extract_interfaces(elements)
|
|
65
|
+
elements.select { |el| el.key?(:interface) }.map do |el|
|
|
66
|
+
process_interface(el[:interface])
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def extract_constants(elements)
|
|
71
|
+
elements.select { |el| el.key?(:const) }.map do |el|
|
|
72
|
+
process_const(el[:const])
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def process_struct(struct_data)
|
|
77
|
+
name = struct_data[:struct_name][:identifier].to_s
|
|
78
|
+
body = struct_data[:body]
|
|
79
|
+
annotations = extract_annotations(struct_data[:annotation])
|
|
80
|
+
|
|
81
|
+
fields = extract_struct_fields(body)
|
|
82
|
+
unions = extract_unions(body)
|
|
83
|
+
groups = extract_groups(body)
|
|
84
|
+
nested_structs = extract_nested_structs(body)
|
|
85
|
+
nested_enums = extract_nested_enums(body)
|
|
86
|
+
nested_interfaces = extract_nested_interfaces(body)
|
|
87
|
+
|
|
88
|
+
Models::Capnproto::StructDefinition.new(
|
|
89
|
+
name: name,
|
|
90
|
+
fields: fields,
|
|
91
|
+
unions: unions,
|
|
92
|
+
groups: groups,
|
|
93
|
+
nested_structs: nested_structs,
|
|
94
|
+
nested_enums: nested_enums,
|
|
95
|
+
nested_interfaces: nested_interfaces,
|
|
96
|
+
annotations: annotations,
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def extract_struct_fields(body)
|
|
101
|
+
return [] unless body
|
|
102
|
+
|
|
103
|
+
Array(body).select do |el|
|
|
104
|
+
el.respond_to?(:key?) && el.key?(:field)
|
|
105
|
+
end.map do |el|
|
|
106
|
+
process_field(el[:field])
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def extract_unions(body)
|
|
111
|
+
return [] unless body
|
|
112
|
+
|
|
113
|
+
Array(body).select do |el|
|
|
114
|
+
el.respond_to?(:key?) && el.key?(:union)
|
|
115
|
+
end.map do |el|
|
|
116
|
+
process_union(el[:union])
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def extract_groups(body)
|
|
121
|
+
return [] unless body
|
|
122
|
+
|
|
123
|
+
Array(body).select do |el|
|
|
124
|
+
el.respond_to?(:key?) && el.key?(:group)
|
|
125
|
+
end.map do |el|
|
|
126
|
+
process_group(el[:group])
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def extract_nested_structs(body)
|
|
131
|
+
return [] unless body
|
|
132
|
+
|
|
133
|
+
Array(body).select do |el|
|
|
134
|
+
el.respond_to?(:key?) && el.key?(:nested_struct)
|
|
135
|
+
end.map do |el|
|
|
136
|
+
process_struct(el[:nested_struct])
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def extract_nested_enums(body)
|
|
141
|
+
return [] unless body
|
|
142
|
+
|
|
143
|
+
Array(body).select do |el|
|
|
144
|
+
el.respond_to?(:key?) && el.key?(:nested_enum)
|
|
145
|
+
end.map do |el|
|
|
146
|
+
process_enum(el[:nested_enum])
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def extract_nested_interfaces(body)
|
|
151
|
+
return [] unless body
|
|
152
|
+
|
|
153
|
+
Array(body).select do |el|
|
|
154
|
+
el.respond_to?(:key?) && el.key?(:nested_interface)
|
|
155
|
+
end.map do |el|
|
|
156
|
+
process_interface(el[:nested_interface])
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def process_field(field_data)
|
|
161
|
+
name = field_data[:name][:identifier].to_s
|
|
162
|
+
ordinal = field_data[:ordinal][:number].to_s.to_i
|
|
163
|
+
type = process_field_type(field_data[:type])
|
|
164
|
+
default_value = process_default_value(field_data[:default])
|
|
165
|
+
|
|
166
|
+
Models::Capnproto::FieldDefinition.new(
|
|
167
|
+
name: name,
|
|
168
|
+
ordinal: ordinal,
|
|
169
|
+
type: type,
|
|
170
|
+
default_value: default_value,
|
|
171
|
+
)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def process_field_type(type_data)
|
|
175
|
+
if type_data[:generic]
|
|
176
|
+
# Generic type: List(T)
|
|
177
|
+
{
|
|
178
|
+
generic: "List",
|
|
179
|
+
element_type: process_field_type(type_data[:generic][:element_type]),
|
|
180
|
+
}
|
|
181
|
+
elsif type_data[:primitive_type]
|
|
182
|
+
type_data[:primitive_type].to_s
|
|
183
|
+
else
|
|
184
|
+
type_data[:user_type][:identifier].to_s
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def process_default_value(default_data)
|
|
189
|
+
return nil unless default_data
|
|
190
|
+
|
|
191
|
+
if default_data[:number]
|
|
192
|
+
val = default_data[:number].to_s
|
|
193
|
+
val.include?(".") ? val.to_f : val.to_i
|
|
194
|
+
elsif default_data[:bool]
|
|
195
|
+
default_data[:bool].to_s == "true"
|
|
196
|
+
elsif default_data[:string]
|
|
197
|
+
default_data[:string].to_s
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def process_union(union_data)
|
|
202
|
+
fields = extract_struct_fields(union_data[:fields])
|
|
203
|
+
|
|
204
|
+
Models::Capnproto::UnionDefinition.new(
|
|
205
|
+
fields: fields,
|
|
206
|
+
)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def process_group(group_data)
|
|
210
|
+
name = group_data[:name][:identifier].to_s
|
|
211
|
+
ordinal = group_data[:ordinal][:number].to_s.to_i
|
|
212
|
+
fields = extract_struct_fields(group_data[:fields])
|
|
213
|
+
|
|
214
|
+
{
|
|
215
|
+
name: name,
|
|
216
|
+
ordinal: ordinal,
|
|
217
|
+
fields: fields.map(&:to_h),
|
|
218
|
+
}
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def process_enum(enum_data)
|
|
222
|
+
name = enum_data[:enum_name][:identifier].to_s
|
|
223
|
+
annotations = extract_annotations(enum_data[:annotation])
|
|
224
|
+
values = {}
|
|
225
|
+
|
|
226
|
+
Array(enum_data[:values]).each do |val_el|
|
|
227
|
+
next unless val_el.respond_to?(:key?)
|
|
228
|
+
|
|
229
|
+
val_name = val_el[:name][:identifier].to_s
|
|
230
|
+
val_ordinal = val_el[:ordinal][:number].to_s.to_i
|
|
231
|
+
|
|
232
|
+
values[val_name] = val_ordinal
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
Models::Capnproto::EnumDefinition.new(
|
|
236
|
+
name: name,
|
|
237
|
+
values: values,
|
|
238
|
+
annotations: annotations,
|
|
239
|
+
)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def process_interface(interface_data)
|
|
243
|
+
name = interface_data[:interface_name][:identifier].to_s
|
|
244
|
+
annotations = extract_annotations(interface_data[:annotation])
|
|
245
|
+
body = interface_data[:body]
|
|
246
|
+
|
|
247
|
+
methods = extract_methods(body)
|
|
248
|
+
|
|
249
|
+
Models::Capnproto::InterfaceDefinition.new(
|
|
250
|
+
name: name,
|
|
251
|
+
methods: methods,
|
|
252
|
+
annotations: annotations,
|
|
253
|
+
)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def extract_methods(body)
|
|
257
|
+
return [] unless body
|
|
258
|
+
|
|
259
|
+
Array(body).select do |el|
|
|
260
|
+
el.respond_to?(:key?) && el.key?(:method)
|
|
261
|
+
end.map do |el|
|
|
262
|
+
process_method(el[:method])
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def process_method(method_data)
|
|
267
|
+
name = method_data[:name][:identifier].to_s
|
|
268
|
+
ordinal = method_data[:ordinal][:number].to_s.to_i
|
|
269
|
+
params = extract_params(method_data[:params])
|
|
270
|
+
results = extract_params(method_data[:results])
|
|
271
|
+
|
|
272
|
+
Models::Capnproto::MethodDefinition.new(
|
|
273
|
+
name: name,
|
|
274
|
+
ordinal: ordinal,
|
|
275
|
+
params: params,
|
|
276
|
+
results: results,
|
|
277
|
+
)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def extract_params(params_data)
|
|
281
|
+
return [] unless params_data
|
|
282
|
+
|
|
283
|
+
Array(params_data).select do |el|
|
|
284
|
+
el.respond_to?(:key?) && el.key?(:param)
|
|
285
|
+
end.map do |el|
|
|
286
|
+
param = el[:param]
|
|
287
|
+
{
|
|
288
|
+
name: param[:name][:identifier].to_s,
|
|
289
|
+
type: process_field_type(param[:type]),
|
|
290
|
+
}
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def process_const(const_data)
|
|
295
|
+
{
|
|
296
|
+
name: const_data[:name][:identifier].to_s,
|
|
297
|
+
type: process_field_type(const_data[:type]),
|
|
298
|
+
value: process_const_value(const_data[:value]),
|
|
299
|
+
}
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def process_const_value(value_data)
|
|
303
|
+
if value_data[:number]
|
|
304
|
+
val = value_data[:number].to_s
|
|
305
|
+
val.include?(".") ? val.to_f : val.to_i
|
|
306
|
+
elsif value_data[:bool]
|
|
307
|
+
value_data[:bool].to_s == "true"
|
|
308
|
+
elsif value_data[:string]
|
|
309
|
+
value_data[:string].to_s
|
|
310
|
+
elsif value_data[:ref]
|
|
311
|
+
value_data[:ref][:identifier].to_s
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def extract_annotations(annotation_data)
|
|
316
|
+
return [] unless annotation_data
|
|
317
|
+
|
|
318
|
+
Array(annotation_data).map do |ann|
|
|
319
|
+
name = ann[:annotation][:identifier].to_s
|
|
320
|
+
value = if ann[:value]
|
|
321
|
+
process_annotation_value(ann[:value])
|
|
322
|
+
else
|
|
323
|
+
true
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
{ name: name, value: value }
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def process_annotation_value(value_data)
|
|
331
|
+
if value_data[:number]
|
|
332
|
+
val = value_data[:number].to_s
|
|
333
|
+
val.include?(".") ? val.to_f : val.to_i
|
|
334
|
+
elsif value_data[:bool]
|
|
335
|
+
value_data[:bool].to_s == "true"
|
|
336
|
+
elsif value_data[:string]
|
|
337
|
+
value_data[:string].to_s
|
|
338
|
+
elsif value_data[:identifier]
|
|
339
|
+
value_data[:identifier].to_s
|
|
340
|
+
else
|
|
341
|
+
value_data.to_s
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unibuf
|
|
4
|
+
module Parsers
|
|
5
|
+
module Capnproto
|
|
6
|
+
# Reader for Cap'n Proto binary segments
|
|
7
|
+
# Cap'n Proto uses word-aligned (8-byte) segments for memory management
|
|
8
|
+
class SegmentReader
|
|
9
|
+
WORD_SIZE = 8 # Cap'n Proto uses 8-byte words
|
|
10
|
+
|
|
11
|
+
attr_reader :segments, :segment_count
|
|
12
|
+
|
|
13
|
+
# Initialize with binary data
|
|
14
|
+
# @param data [String] Binary data to read
|
|
15
|
+
def initialize(data)
|
|
16
|
+
@data = data
|
|
17
|
+
@segments = []
|
|
18
|
+
@segment_count = 0
|
|
19
|
+
parse_segments
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Read a word (8 bytes) from a segment at given offset
|
|
23
|
+
# @param segment_id [Integer] Segment index
|
|
24
|
+
# @param word_offset [Integer] Word offset within segment
|
|
25
|
+
# @return [Integer] 64-bit word value
|
|
26
|
+
def read_word(segment_id, word_offset)
|
|
27
|
+
if segment_id >= @segment_count
|
|
28
|
+
raise ArgumentError,
|
|
29
|
+
"Invalid segment ID"
|
|
30
|
+
end
|
|
31
|
+
raise ArgumentError, "Invalid word offset" if word_offset.negative?
|
|
32
|
+
|
|
33
|
+
segment = @segments[segment_id]
|
|
34
|
+
byte_offset = word_offset * WORD_SIZE
|
|
35
|
+
|
|
36
|
+
if byte_offset + WORD_SIZE > segment.size
|
|
37
|
+
raise ArgumentError,
|
|
38
|
+
"Offset out of bounds"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Read 8 bytes as little-endian 64-bit unsigned integer
|
|
42
|
+
segment[byte_offset, WORD_SIZE].unpack1("Q<")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Read multiple words from a segment
|
|
46
|
+
# @param segment_id [Integer] Segment index
|
|
47
|
+
# @param word_offset [Integer] Starting word offset
|
|
48
|
+
# @param count [Integer] Number of words to read
|
|
49
|
+
# @return [Array<Integer>] Array of word values
|
|
50
|
+
def read_words(segment_id, word_offset, count)
|
|
51
|
+
(0...count).map { |i| read_word(segment_id, word_offset + i) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Read bytes from a segment
|
|
55
|
+
# @param segment_id [Integer] Segment index
|
|
56
|
+
# @param byte_offset [Integer] Byte offset within segment
|
|
57
|
+
# @param length [Integer] Number of bytes to read
|
|
58
|
+
# @return [String] Binary data
|
|
59
|
+
def read_bytes(segment_id, byte_offset, length)
|
|
60
|
+
if segment_id >= @segment_count
|
|
61
|
+
raise ArgumentError,
|
|
62
|
+
"Invalid segment ID"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
segment = @segments[segment_id]
|
|
66
|
+
if byte_offset + length > segment.size
|
|
67
|
+
raise ArgumentError,
|
|
68
|
+
"Offset out of bounds"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
segment[byte_offset, length]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Get segment size in words
|
|
75
|
+
# @param segment_id [Integer] Segment index
|
|
76
|
+
# @return [Integer] Size in words
|
|
77
|
+
def segment_size(segment_id)
|
|
78
|
+
if segment_id >= @segment_count
|
|
79
|
+
raise ArgumentError,
|
|
80
|
+
"Invalid segment ID"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
@segments[segment_id].size / WORD_SIZE
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Check if a segment exists
|
|
87
|
+
# @param segment_id [Integer] Segment index
|
|
88
|
+
# @return [Boolean]
|
|
89
|
+
def segment_exists?(segment_id)
|
|
90
|
+
segment_id >= 0 && segment_id < @segment_count
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def parse_segments
|
|
96
|
+
return if @data.nil? || @data.empty?
|
|
97
|
+
|
|
98
|
+
# Read segment count (first 4 bytes as little-endian 32-bit integer)
|
|
99
|
+
# Segment count: (N + 1) where N is in the first 4 bytes
|
|
100
|
+
segment_count_minus_one = @data[0, 4].unpack1("L<")
|
|
101
|
+
@segment_count = segment_count_minus_one + 1
|
|
102
|
+
|
|
103
|
+
# Read segment sizes (each is a 4-byte little-endian integer)
|
|
104
|
+
# Segment sizes start at byte 4
|
|
105
|
+
offset = 4
|
|
106
|
+
segment_sizes = []
|
|
107
|
+
|
|
108
|
+
@segment_count.times do
|
|
109
|
+
size = @data[offset, 4].unpack1("L<")
|
|
110
|
+
segment_sizes << size
|
|
111
|
+
offset += 4
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Align to 8-byte boundary after segment table
|
|
115
|
+
# Header size is 4 + (segment_count * 4) = 4 * (1 + segment_count)
|
|
116
|
+
# If segment count is ODD, header is divisible by 8 (no padding needed)
|
|
117
|
+
# If segment count is EVEN, header needs 4 bytes padding to align to 8
|
|
118
|
+
offset += 4 unless @segment_count.odd?
|
|
119
|
+
|
|
120
|
+
# Read each segment
|
|
121
|
+
segment_sizes.each do |size_in_words|
|
|
122
|
+
size_in_bytes = size_in_words * WORD_SIZE
|
|
123
|
+
segment_data = @data[offset, size_in_bytes]
|
|
124
|
+
@segments << segment_data
|
|
125
|
+
offset += size_in_bytes
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "segment_reader"
|
|
4
|
+
require_relative "pointer_decoder"
|
|
5
|
+
|
|
6
|
+
module Unibuf
|
|
7
|
+
module Parsers
|
|
8
|
+
module Capnproto
|
|
9
|
+
# Reader for Cap'n Proto struct data
|
|
10
|
+
# Structs have two sections: data (inline primitives) and pointers
|
|
11
|
+
class StructReader
|
|
12
|
+
attr_reader :segment_reader, :segment_id, :word_offset, :data_words,
|
|
13
|
+
:pointer_words
|
|
14
|
+
|
|
15
|
+
# Initialize struct reader
|
|
16
|
+
# @param segment_reader [SegmentReader] Segment reader
|
|
17
|
+
# @param segment_id [Integer] Segment containing the struct
|
|
18
|
+
# @param word_offset [Integer] Word offset of struct start
|
|
19
|
+
# @param data_words [Integer] Number of data words
|
|
20
|
+
# @param pointer_words [Integer] Number of pointer words
|
|
21
|
+
def initialize(segment_reader, segment_id, word_offset, data_words,
|
|
22
|
+
pointer_words)
|
|
23
|
+
@segment_reader = segment_reader
|
|
24
|
+
@segment_id = segment_id
|
|
25
|
+
@word_offset = word_offset
|
|
26
|
+
@data_words = data_words
|
|
27
|
+
@pointer_words = pointer_words
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Read a primitive field from data section
|
|
31
|
+
# @param word_index [Integer] Word index in data section
|
|
32
|
+
# @param bit_offset [Integer] Bit offset within word (0-63)
|
|
33
|
+
# @param bit_width [Integer] Width in bits
|
|
34
|
+
# @return [Integer] Field value
|
|
35
|
+
def read_data_field(word_index, bit_offset = 0, bit_width = 64)
|
|
36
|
+
return 0 if word_index >= @data_words
|
|
37
|
+
|
|
38
|
+
word = @segment_reader.read_word(@segment_id,
|
|
39
|
+
@word_offset + word_index)
|
|
40
|
+
|
|
41
|
+
# Extract bits
|
|
42
|
+
mask = (1 << bit_width) - 1
|
|
43
|
+
(word >> bit_offset) & mask
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Read an 8-bit integer
|
|
47
|
+
# @param word_index [Integer] Word index
|
|
48
|
+
# @param byte_offset [Integer] Byte offset within word (0-7)
|
|
49
|
+
# @return [Integer]
|
|
50
|
+
def read_int8(word_index, byte_offset = 0)
|
|
51
|
+
value = read_data_field(word_index, byte_offset * 8, 8)
|
|
52
|
+
# Convert to signed
|
|
53
|
+
value >= 128 ? value - 256 : value
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Read an unsigned 8-bit integer
|
|
57
|
+
# @param word_index [Integer] Word index
|
|
58
|
+
# @param byte_offset [Integer] Byte offset within word (0-7)
|
|
59
|
+
# @return [Integer]
|
|
60
|
+
def read_uint8(word_index, byte_offset = 0)
|
|
61
|
+
read_data_field(word_index, byte_offset * 8, 8)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Read a 16-bit integer
|
|
65
|
+
# @param word_index [Integer] Word index
|
|
66
|
+
# @param half_word_offset [Integer] Half-word offset (0-3)
|
|
67
|
+
# @return [Integer]
|
|
68
|
+
def read_int16(word_index, half_word_offset = 0)
|
|
69
|
+
value = read_data_field(word_index, half_word_offset * 16, 16)
|
|
70
|
+
# Convert to signed
|
|
71
|
+
value >= 32768 ? value - 65536 : value
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Read an unsigned 16-bit integer
|
|
75
|
+
# @param word_index [Integer] Word index
|
|
76
|
+
# @param half_word_offset [Integer] Half-word offset (0-3)
|
|
77
|
+
# @return [Integer]
|
|
78
|
+
def read_uint16(word_index, half_word_offset = 0)
|
|
79
|
+
read_data_field(word_index, half_word_offset * 16, 16)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Read a 32-bit integer
|
|
83
|
+
# @param word_index [Integer] Word index
|
|
84
|
+
# @param dword_offset [Integer] Double-word offset (0-1)
|
|
85
|
+
# @return [Integer]
|
|
86
|
+
def read_int32(word_index, dword_offset = 0)
|
|
87
|
+
value = read_data_field(word_index, dword_offset * 32, 32)
|
|
88
|
+
# Convert to signed
|
|
89
|
+
value >= 2147483648 ? value - 4294967296 : value
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Read an unsigned 32-bit integer
|
|
93
|
+
# @param word_index [Integer] Word index
|
|
94
|
+
# @param dword_offset [Integer] Double-word offset (0-1)
|
|
95
|
+
# @return [Integer]
|
|
96
|
+
def read_uint32(word_index, dword_offset = 0)
|
|
97
|
+
read_data_field(word_index, dword_offset * 32, 32)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Read a 64-bit integer
|
|
101
|
+
# @param word_index [Integer] Word index
|
|
102
|
+
# @return [Integer]
|
|
103
|
+
def read_int64(word_index)
|
|
104
|
+
value = read_data_field(word_index, 0, 64)
|
|
105
|
+
# Convert to signed
|
|
106
|
+
value >= 9223372036854775808 ? value - 18446744073709551616 : value
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Read an unsigned 64-bit integer
|
|
110
|
+
# @param word_index [Integer] Word index
|
|
111
|
+
# @return [Integer]
|
|
112
|
+
def read_uint64(word_index)
|
|
113
|
+
read_data_field(word_index, 0, 64)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Read a 32-bit float
|
|
117
|
+
# @param word_index [Integer] Word index
|
|
118
|
+
# @param dword_offset [Integer] Double-word offset (0-1)
|
|
119
|
+
# @return [Float]
|
|
120
|
+
def read_float32(word_index, dword_offset = 0)
|
|
121
|
+
bits = read_uint32(word_index, dword_offset)
|
|
122
|
+
[bits].pack("L").unpack1("f")
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Read a 64-bit float
|
|
126
|
+
# @param word_index [Integer] Word index
|
|
127
|
+
# @return [Float]
|
|
128
|
+
def read_float64(word_index)
|
|
129
|
+
bits = read_uint64(word_index)
|
|
130
|
+
[bits].pack("Q").unpack1("d")
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Read a boolean
|
|
134
|
+
# @param word_index [Integer] Word index
|
|
135
|
+
# @param bit_offset [Integer] Bit offset within word
|
|
136
|
+
# @return [Boolean]
|
|
137
|
+
def read_bool(word_index, bit_offset = 0)
|
|
138
|
+
read_data_field(word_index, bit_offset, 1) == 1
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Read a pointer from pointer section
|
|
142
|
+
# @param pointer_index [Integer] Pointer index in pointer section
|
|
143
|
+
# @return [Hash, nil] Decoded pointer or nil
|
|
144
|
+
def read_pointer(pointer_index)
|
|
145
|
+
return nil if pointer_index >= @pointer_words
|
|
146
|
+
|
|
147
|
+
pointer_word_offset = @word_offset + @data_words + pointer_index
|
|
148
|
+
pointer_word = @segment_reader.read_word(@segment_id,
|
|
149
|
+
pointer_word_offset)
|
|
150
|
+
|
|
151
|
+
return nil if pointer_word.zero?
|
|
152
|
+
|
|
153
|
+
PointerDecoder.decode(pointer_word)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Follow a pointer to get the target location
|
|
157
|
+
# @param pointer_index [Integer] Pointer index
|
|
158
|
+
# @return [Hash, nil] Target location info or nil
|
|
159
|
+
def follow_pointer(pointer_index)
|
|
160
|
+
pointer = read_pointer(pointer_index)
|
|
161
|
+
return nil unless pointer
|
|
162
|
+
return nil if pointer[:type] == :null
|
|
163
|
+
|
|
164
|
+
case pointer[:type]
|
|
165
|
+
when :struct
|
|
166
|
+
# Struct pointer points relative to its own position
|
|
167
|
+
pointer_position = @word_offset + @data_words + pointer_index
|
|
168
|
+
target_offset = pointer_position + 1 + pointer[:offset]
|
|
169
|
+
{
|
|
170
|
+
type: :struct,
|
|
171
|
+
segment_id: @segment_id,
|
|
172
|
+
word_offset: target_offset,
|
|
173
|
+
data_words: pointer[:data_words],
|
|
174
|
+
pointer_words: pointer[:pointer_words],
|
|
175
|
+
}
|
|
176
|
+
when :list
|
|
177
|
+
# List pointer points relative to its own position
|
|
178
|
+
pointer_position = @word_offset + @data_words + pointer_index
|
|
179
|
+
target_offset = pointer_position + 1 + pointer[:offset]
|
|
180
|
+
{
|
|
181
|
+
type: :list,
|
|
182
|
+
segment_id: @segment_id,
|
|
183
|
+
word_offset: target_offset,
|
|
184
|
+
element_size: pointer[:element_size],
|
|
185
|
+
element_count: pointer[:element_count],
|
|
186
|
+
}
|
|
187
|
+
when :far
|
|
188
|
+
# Far pointer points to another segment
|
|
189
|
+
{
|
|
190
|
+
type: :far,
|
|
191
|
+
segment_id: pointer[:segment_id],
|
|
192
|
+
word_offset: pointer[:offset],
|
|
193
|
+
}
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|