nl 0.3.0 → 0.4.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/CHANGELOG.md +15 -0
- data/Rakefile +7 -0
- data/lib/nl/async/dispatcher.rb +16 -16
- data/lib/nl/async/driver.rb +0 -2
- data/lib/nl/async/mailbox.rb +0 -2
- data/lib/nl/async/operation.rb +0 -2
- data/lib/nl/async.rb +0 -2
- data/lib/nl/attribute_set.rb +173 -0
- data/lib/nl/bitfield32.rb +86 -0
- data/lib/nl/blocking_transport.rb +12 -12
- data/lib/nl/connection.rb +7 -9
- data/lib/nl/datagram.rb +7 -9
- data/lib/nl/datatypes.rb +454 -0
- data/lib/nl/decoder.rb +4 -0
- data/lib/nl/endian.rb +9 -0
- data/lib/nl/exchange.rb +40 -18
- data/lib/nl/family.rb +50 -55
- data/lib/nl/genl/client.rb +105 -0
- data/lib/nl/genl/protocol.rb +59 -0
- data/lib/nl/genl/wire.rb +95 -0
- data/lib/nl/genl.rb +60 -62
- data/lib/nl/notification.rb +3 -6
- data/lib/nl/notification_router.rb +30 -23
- data/lib/nl/raw/client.rb +83 -0
- data/lib/nl/raw/protocol.rb +112 -0
- data/lib/nl/raw/wire.rb +147 -0
- data/lib/nl/raw.rb +104 -0
- data/lib/nl/sequence_allocator.rb +0 -2
- data/lib/nl/socket.rb +21 -5
- data/lib/nl/structured_payload.rb +61 -0
- data/lib/nl/sub_message.rb +94 -0
- data/lib/nl/version.rb +1 -1
- data/lib/nl.rb +5 -6
- data/sig/generated/nl/async/dispatcher.rbs +53 -0
- data/sig/generated/nl/async/driver.rbs +21 -0
- data/sig/generated/nl/async/mailbox.rbs +32 -0
- data/sig/generated/nl/async/operation.rbs +123 -0
- data/sig/generated/nl/async.rbs +9 -0
- data/sig/generated/nl/attribute_set.rbs +50 -0
- data/sig/generated/nl/bitfield32.rbs +41 -0
- data/sig/generated/nl/blocking_transport.rbs +33 -0
- data/sig/generated/nl/connection.rbs +26 -0
- data/sig/generated/nl/datagram.rbs +11 -0
- data/sig/generated/nl/datatypes.rbs +181 -0
- data/sig/generated/nl/decoder.rbs +38 -0
- data/sig/generated/nl/encoder.rbs +29 -0
- data/sig/generated/nl/endian.rbs +29 -0
- data/sig/generated/nl/error.rbs +27 -0
- data/sig/generated/nl/exchange.rbs +75 -0
- data/sig/generated/nl/family.rbs +89 -0
- data/sig/generated/nl/genl/client.rbs +74 -0
- data/sig/generated/nl/genl/protocol.rbs +37 -0
- data/sig/generated/nl/genl/wire.rbs +113 -0
- data/sig/generated/nl/genl.rbs +50 -0
- data/sig/generated/nl/notification.rbs +53 -0
- data/sig/generated/nl/notification_router.rbs +30 -0
- data/sig/generated/nl/raw/client.rbs +49 -0
- data/sig/generated/nl/raw/protocol.rbs +122 -0
- data/sig/generated/nl/raw/wire.rbs +151 -0
- data/sig/generated/nl/raw.rbs +70 -0
- data/sig/generated/nl/sequence_allocator.rbs +15 -0
- data/sig/generated/nl/socket.rbs +59 -0
- data/sig/generated/nl/structured_payload.rbs +25 -0
- data/sig/generated/nl/sub_message.rbs +74 -0
- data/sig/generated/nl/version.rbs +5 -0
- data/sig/generated/nl.rbs +2 -0
- metadata +48 -7
- data/lib/nl/core.rb +0 -94
- data/lib/nl/genl/connection.rb +0 -89
- data/lib/nl/protocols/genl.rb +0 -74
- data/lib/nl/protocols/raw.rb +0 -460
data/lib/nl/datatypes.rb
ADDED
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
require_relative 'raw/wire'
|
|
2
|
+
require_relative 'bitfield32'
|
|
3
|
+
|
|
4
|
+
module Nl
|
|
5
|
+
# @rbs!
|
|
6
|
+
# interface _DataType
|
|
7
|
+
# def encode: (Encoder, untyped, ?context: untyped) -> void
|
|
8
|
+
# def decode: (Decoder, ?context: untyped, ?nlattr_type_flags: Integer) -> untyped
|
|
9
|
+
# def nlattr_type_flags: (?untyped, ?context: untyped) -> Integer
|
|
10
|
+
# def coerce: (untyped, ?context: untyped) -> untyped
|
|
11
|
+
# end
|
|
12
|
+
|
|
13
|
+
module DataTypes
|
|
14
|
+
class Base
|
|
15
|
+
def initialize(check: nil)
|
|
16
|
+
@check = check
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def nlattr_type_flags(_value = nil, context: nil)
|
|
20
|
+
0
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def coerce(value, context: nil)
|
|
24
|
+
value
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private def checked(value)
|
|
28
|
+
@check&.call(value)
|
|
29
|
+
value
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class Scalar < Base
|
|
34
|
+
def initialize(type, check:)
|
|
35
|
+
super(check:)
|
|
36
|
+
@type = type
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def encode(encoder, value, context: nil)
|
|
40
|
+
encoder.put_value(@type, checked(value || 0))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def decode(decoder, context: nil, nlattr_type_flags: 0)
|
|
44
|
+
checked(decoder.get_value(@type))
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class VariableInteger < Base
|
|
49
|
+
UINT32_RANGE = (0...2**32)
|
|
50
|
+
UINT64_RANGE = (0...2**64)
|
|
51
|
+
SINT32_RANGE = (-(2**31)...2**31)
|
|
52
|
+
SINT64_RANGE = (-(2**63)...2**63)
|
|
53
|
+
|
|
54
|
+
def initialize(byte_order, signed:, check:)
|
|
55
|
+
super(check:)
|
|
56
|
+
prefix = signed ? 'S' : 'U'
|
|
57
|
+
@type32 = byte_order.const_get("#{prefix}32")
|
|
58
|
+
@type64 = byte_order.const_get("#{prefix}64")
|
|
59
|
+
@range32 = signed ? SINT32_RANGE : UINT32_RANGE
|
|
60
|
+
@range64 = signed ? SINT64_RANGE : UINT64_RANGE
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def encode(encoder, value, context: nil)
|
|
64
|
+
value = checked(value || 0)
|
|
65
|
+
unless @range64.cover?(value)
|
|
66
|
+
raise RangeError, "integer #{value.inspect} is outside the #{@range64.begin}...#{@range64.end} range"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
type = @range32.cover?(value) ? @type32 : @type64
|
|
70
|
+
encoder.put_value(type, value)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def decode(decoder, context: nil, nlattr_type_flags: 0)
|
|
74
|
+
type = case decoder.remaining
|
|
75
|
+
when 4 then @type32
|
|
76
|
+
when 8 then @type64
|
|
77
|
+
else
|
|
78
|
+
raise Decoder::Error, "variable integer payload must be 4 or 8 bytes, got #{decoder.remaining}"
|
|
79
|
+
end
|
|
80
|
+
checked(decoder.get_value(type))
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
class String < Base
|
|
85
|
+
def initialize(check:)
|
|
86
|
+
super(check:)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def encode(encoder, value, context: nil)
|
|
90
|
+
encoder.put_zstring(checked(value))
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def decode(decoder, context: nil, nlattr_type_flags: 0)
|
|
94
|
+
checked(decoder.get_zstring)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
class Binary < Base
|
|
99
|
+
def initialize(length: nil, check:)
|
|
100
|
+
super(check:)
|
|
101
|
+
@length = length
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def encode(encoder, value, context: nil)
|
|
105
|
+
value = checked(value)
|
|
106
|
+
if @length && value.bytesize != @length
|
|
107
|
+
raise ArgumentError, "binary value must be exactly #{@length} bytes, got #{value.bytesize}"
|
|
108
|
+
end
|
|
109
|
+
encoder.put_string(value)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def decode(decoder, context: nil, nlattr_type_flags: 0)
|
|
113
|
+
checked(decoder.get_string(@length || decoder.remaining))
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# An array whose elements are packed consecutively without per-element
|
|
118
|
+
# headers or alignment padding.
|
|
119
|
+
class PackedArray < Base
|
|
120
|
+
def initialize(sub_type, check: nil)
|
|
121
|
+
super(check:)
|
|
122
|
+
@sub_type = sub_type
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def encode(encoder, values, context: nil)
|
|
126
|
+
values = coerce(values, context:)
|
|
127
|
+
if @check
|
|
128
|
+
temporary = Encoder.new
|
|
129
|
+
values.each { @sub_type.encode(temporary, it, context:) }
|
|
130
|
+
encoder.put_string(checked(temporary.buffer.get_string))
|
|
131
|
+
else
|
|
132
|
+
values.each { @sub_type.encode(encoder, it, context:) }
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def coerce(values, context: nil)
|
|
137
|
+
values.map { @sub_type.coerce(it, context:) }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def decode(decoder, context: nil, nlattr_type_flags: 0)
|
|
141
|
+
if @check
|
|
142
|
+
payload = checked(decoder.get_string)
|
|
143
|
+
decoder = Decoder.new(IO::Buffer.for(payload))
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
result = []
|
|
147
|
+
result << @sub_type.decode(decoder, context:, nlattr_type_flags: 0) while decoder.available?
|
|
148
|
+
result
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
class Struct < Base
|
|
153
|
+
def initialize(type, check: nil, consume_remaining: false)
|
|
154
|
+
super(check:)
|
|
155
|
+
@type = type
|
|
156
|
+
@consume_remaining = consume_remaining
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def coerce(value, context: nil)
|
|
160
|
+
return value unless value.is_a?(Hash)
|
|
161
|
+
|
|
162
|
+
unknown = value.keys - @type::MEMBERS.keys
|
|
163
|
+
raise ArgumentError, "unknown struct members: #{unknown.join(', ')}" unless unknown.empty?
|
|
164
|
+
|
|
165
|
+
@type.new(*@type::MEMBERS.map { |name, datatype| datatype.coerce(value[name], context:) })
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def encode(encoder, value, context: nil)
|
|
169
|
+
unless value.is_a?(@type)
|
|
170
|
+
raise TypeError, "value must be an instance of #{@type}"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
if @check
|
|
174
|
+
temporary = Encoder.new
|
|
175
|
+
value.encode(temporary)
|
|
176
|
+
encoder.put_string(checked(temporary.buffer.get_string))
|
|
177
|
+
else
|
|
178
|
+
value.encode(encoder)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def decode(decoder, context: nil, nlattr_type_flags: 0)
|
|
183
|
+
unless @check
|
|
184
|
+
value = @type.decode(decoder)
|
|
185
|
+
decoder.skip if @consume_remaining
|
|
186
|
+
return value
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
payload = checked(decoder.get_string)
|
|
190
|
+
nested = Decoder.new(IO::Buffer.for(payload))
|
|
191
|
+
nested.limit(payload.bytesize) do
|
|
192
|
+
value = @type.decode(it)
|
|
193
|
+
it.skip if @consume_remaining
|
|
194
|
+
value
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
class Flag < Base
|
|
200
|
+
def encode(encoder, value, context: nil)
|
|
201
|
+
# flag attribute has no payload; presence encodes true
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def decode(decoder, context: nil, nlattr_type_flags: 0)
|
|
205
|
+
true
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
class Bitfield32 < Base
|
|
210
|
+
def encode(encoder, value, context: nil)
|
|
211
|
+
unless value.is_a?(Nl::Bitfield32)
|
|
212
|
+
raise TypeError, 'bitfield32 value must be an Nl::Bitfield32'
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
encoder.put_value(Endian::Host::U32, value.value)
|
|
216
|
+
encoder.put_value(Endian::Host::U32, value.selector)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def decode(decoder, context: nil, nlattr_type_flags: 0)
|
|
220
|
+
value = decoder.get_value(Endian::Host::U32)
|
|
221
|
+
selector = decoder.get_value(Endian::Host::U32)
|
|
222
|
+
Nl::Bitfield32.new(value, selector)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
class Pad < Base
|
|
227
|
+
def initialize(length = nil)
|
|
228
|
+
@length = length
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def encode(encoder, _value, context: nil)
|
|
232
|
+
encoder.put_string(?\0.b * @length) if @length
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def decode(decoder, context: nil, nlattr_type_flags: 0)
|
|
236
|
+
@length ? decoder.skip(@length) : decoder.skip
|
|
237
|
+
nil
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
class NestedAttributes < Base
|
|
242
|
+
def initialize(attribute_set, selector_bindings: [])
|
|
243
|
+
@attribute_set = attribute_set
|
|
244
|
+
@selector_bindings = selector_bindings
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def nlattr_type_flags(_value = nil, context: nil)
|
|
248
|
+
Raw::NLA_F_NESTED
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def coerce(value, context: nil)
|
|
252
|
+
value.is_a?(Hash) ? @attribute_set.build_attributes(value, external_selectors: selectors(context)) : value
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def encode(encoder, value, context: nil)
|
|
256
|
+
unless value.is_a?(@attribute_set)
|
|
257
|
+
raise TypeError, "value must be an instance of #{@attribute_set}"
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
value.encode(encoder, external_selectors: selectors(context))
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def decode(decoder, context: nil, nlattr_type_flags: 0)
|
|
264
|
+
@attribute_set.decode(decoder, external_selectors: selectors(context))
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
private def selectors(context)
|
|
268
|
+
@selector_bindings.map { it.read(context) }
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
class SubMessage < Base
|
|
273
|
+
Format = Data.define(:type, :selector_bindings, :nested)
|
|
274
|
+
|
|
275
|
+
def initialize(selector, formats)
|
|
276
|
+
@selector = selector
|
|
277
|
+
@formats = formats.transform_values do |format|
|
|
278
|
+
format.is_a?(Format) ? format : Format.new(*format)
|
|
279
|
+
end.freeze
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def nlattr_type_flags(value = nil, context:)
|
|
283
|
+
if value.is_a?(RawSubMessage)
|
|
284
|
+
@selector.read(context)
|
|
285
|
+
return value.nlattr_type_flags
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
format = @selector.select(context, @formats)
|
|
289
|
+
unless value.is_a?(format.type)
|
|
290
|
+
raise TypeError, "value must be an instance of #{format.type}"
|
|
291
|
+
end
|
|
292
|
+
format.nested ? Raw::NLA_F_NESTED : 0
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def coerce(value, context:)
|
|
296
|
+
if value.is_a?(RawSubMessage)
|
|
297
|
+
@selector.read(context) # ensure selector value is present
|
|
298
|
+
return value
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
format = @selector.select(context, @formats)
|
|
302
|
+
return value if value.is_a?(format.type)
|
|
303
|
+
return format.type.from_params(value, external_selectors: selectors(format, context)) if value.is_a?(Hash)
|
|
304
|
+
|
|
305
|
+
raise TypeError, "value must be a Hash or an instance of #{format.type}"
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def encode(encoder, value, context: nil)
|
|
309
|
+
if value.is_a?(RawSubMessage)
|
|
310
|
+
@selector.read(context)
|
|
311
|
+
return value.encode(encoder)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
format = @selector.select(context, @formats)
|
|
315
|
+
unless value.is_a?(format.type)
|
|
316
|
+
raise TypeError, "value must be an instance of #{format.type}"
|
|
317
|
+
end
|
|
318
|
+
value.encode(encoder, external_selectors: selectors(format, context))
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def decode(decoder, context:, nlattr_type_flags: 0)
|
|
322
|
+
format = @selector.select(context, @formats) do
|
|
323
|
+
return RawSubMessage.new(
|
|
324
|
+
decoder.get_string,
|
|
325
|
+
nlattr_type_flags:,
|
|
326
|
+
)
|
|
327
|
+
end
|
|
328
|
+
format.type.decode(decoder, external_selectors: selectors(format, context))
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
private def selectors(format, context)
|
|
332
|
+
format.selector_bindings.map { it.read(context) }
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# Nested attributes whose type numbers are keys rather than members of
|
|
337
|
+
# an attribute set. Each level is returned as an Integer-keyed Hash;
|
|
338
|
+
# values at the innermost level are decoded as @attribute_set.
|
|
339
|
+
class NestTypeValue < Base
|
|
340
|
+
def initialize(attribute_set, levels, selector_bindings: [])
|
|
341
|
+
raise ArgumentError, 'levels must be positive' unless levels.positive?
|
|
342
|
+
|
|
343
|
+
@attribute_set = attribute_set
|
|
344
|
+
@levels = levels
|
|
345
|
+
@selector_bindings = selector_bindings
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def encode(encoder, value, context: nil)
|
|
349
|
+
encode_level(encoder, value, @levels, selectors(context))
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def coerce(value = nil, context: nil, **keywords)
|
|
353
|
+
value = keywords unless keywords.empty?
|
|
354
|
+
coerce_level(value, @levels, selectors(context))
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def decode(decoder, context: nil, nlattr_type_flags: 0)
|
|
358
|
+
decode_level(decoder, @levels, selectors(context))
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
private def encode_level(encoder, values, levels, external_selectors)
|
|
362
|
+
values.each do |type, value|
|
|
363
|
+
nlattr = Raw::NlAttr.new(0, type | Raw::NLA_F_NESTED)
|
|
364
|
+
encoder.measure(Endian::Host::U16) do
|
|
365
|
+
nlattr.encode(encoder)
|
|
366
|
+
if levels == 1
|
|
367
|
+
if external_selectors.empty?
|
|
368
|
+
value.encode(encoder)
|
|
369
|
+
else
|
|
370
|
+
value.encode(encoder, external_selectors:)
|
|
371
|
+
end
|
|
372
|
+
else
|
|
373
|
+
encode_level(encoder, value, levels - 1, external_selectors)
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
encoder.align_to(Raw::NLA_ALIGNTO)
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
private def coerce_level(values, levels, external_selectors)
|
|
381
|
+
values.transform_values do |value|
|
|
382
|
+
if levels == 1
|
|
383
|
+
if value.is_a?(Hash)
|
|
384
|
+
external_selectors.empty? ? @attribute_set.build_attributes(value) :
|
|
385
|
+
@attribute_set.build_attributes(value, external_selectors:)
|
|
386
|
+
else
|
|
387
|
+
value
|
|
388
|
+
end
|
|
389
|
+
else
|
|
390
|
+
coerce_level(value, levels - 1, external_selectors)
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
private def decode_level(decoder, levels, external_selectors)
|
|
396
|
+
result = {}
|
|
397
|
+
while decoder.available?(Raw::NLA_HDRLEN)
|
|
398
|
+
nlattr = Raw::NlAttr.decode(decoder)
|
|
399
|
+
type = nlattr.type & Raw::NLA_TYPE_MASK
|
|
400
|
+
value = decoder.limit(nlattr.len - Raw::NLA_HDRLEN) do
|
|
401
|
+
if levels == 1
|
|
402
|
+
external_selectors.empty? ? @attribute_set.decode(decoder) :
|
|
403
|
+
@attribute_set.decode(decoder, external_selectors:)
|
|
404
|
+
else
|
|
405
|
+
decode_level(decoder, levels - 1, external_selectors)
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
decoder.align_to(Raw::NLA_ALIGNTO)
|
|
409
|
+
result[type] = value
|
|
410
|
+
end
|
|
411
|
+
result
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
private def selectors(context)
|
|
415
|
+
@selector_bindings.map { it.read(context) }
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
class IndexedArray < Base
|
|
420
|
+
def initialize(sub_type)
|
|
421
|
+
@sub_type = sub_type
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def encode(encoder, values, context: nil)
|
|
425
|
+
values.each_with_index do |value, i|
|
|
426
|
+
nlattr = Raw::NlAttr.new(0, i + 1)
|
|
427
|
+
encoder.measure(Endian::Host::U16) do
|
|
428
|
+
nlattr.encode(encoder)
|
|
429
|
+
@sub_type.encode(encoder, value, context:)
|
|
430
|
+
end
|
|
431
|
+
encoder.align_to(Raw::NLA_ALIGNTO)
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def coerce(values, context: nil)
|
|
436
|
+
values.map { @sub_type.coerce(it, context:) }
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
def decode(decoder, context: nil, nlattr_type_flags: 0)
|
|
440
|
+
result = []
|
|
441
|
+
while decoder.available?
|
|
442
|
+
nlattr = Raw::NlAttr.decode(decoder)
|
|
443
|
+
flags = nlattr.type & (Raw::NLA_F_NESTED | Raw::NLA_F_NET_BYTEORDER)
|
|
444
|
+
element = decoder.limit(nlattr.len - Raw::NLA_HDRLEN) do
|
|
445
|
+
@sub_type.decode(decoder, context:, nlattr_type_flags: flags)
|
|
446
|
+
end
|
|
447
|
+
decoder.align_to(Raw::NLA_ALIGNTO)
|
|
448
|
+
result << element
|
|
449
|
+
end
|
|
450
|
+
result
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
end
|
data/lib/nl/decoder.rb
CHANGED
data/lib/nl/endian.rb
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
module Nl
|
|
2
|
+
# Byte-order helpers
|
|
2
3
|
module Endian
|
|
4
|
+
# sizeof(int)
|
|
3
5
|
SIZEOF_INT = [1].pack('i!').bytesize
|
|
6
|
+
# sizeof(long)
|
|
4
7
|
SIZEOF_LONG = [1].pack('l!').bytesize
|
|
8
|
+
# sizeof(long long)
|
|
5
9
|
SIZEOF_LLONG = [1].pack('q!').bytesize
|
|
6
10
|
|
|
11
|
+
# Little-endian scalar types.
|
|
7
12
|
module Little
|
|
8
13
|
U8, S8, U16, S16, U32, S32, U64, S64, F32, F64 = :U8, :S8, :u16, :s16, :u32, :s32, :u64, :s64, :f32, :f64
|
|
9
14
|
end
|
|
10
15
|
|
|
16
|
+
# Big-endian scalar types.
|
|
11
17
|
module Big
|
|
12
18
|
U8, S8, U16, S16, U32, S32, U64, S64, F32, F64 = :U8, :S8, :U16, :S16, :U32, :S32, :U64, :S64, :F32, :F64
|
|
13
19
|
end
|
|
14
20
|
|
|
21
|
+
# Host-endian scalar types.
|
|
22
|
+
#
|
|
23
|
+
# This module includes either {Little} or {Big} depending on the host's native byte order.
|
|
15
24
|
module Host
|
|
16
25
|
include (IO::Buffer::HOST_ENDIAN == IO::Buffer::LITTLE_ENDIAN ? Little : Big)
|
|
17
26
|
|
data/lib/nl/exchange.rb
CHANGED
|
@@ -1,23 +1,44 @@
|
|
|
1
|
-
# rbs_inline: enabled
|
|
2
|
-
|
|
3
1
|
require_relative 'error'
|
|
4
|
-
require_relative '
|
|
2
|
+
require_relative 'raw/protocol'
|
|
5
3
|
|
|
6
4
|
module Nl
|
|
7
5
|
# State machine for one Netlink request/reply exchange.
|
|
6
|
+
#
|
|
7
|
+
# Dump exchanges are known to be multipart from the request and remain open
|
|
8
|
+
# through NLMSG_DONE. Some Linux Generic Netlink dump handlers construct
|
|
9
|
+
# replies with genlmsg_iput(), which leaves NLM_F_MULTI unset despite that
|
|
10
|
+
# multipart lifetime. Dump mode therefore accepts data with or without the
|
|
11
|
+
# flag; reply mode still uses it to detect a multipart do response.
|
|
12
|
+
#
|
|
13
|
+
# Some operations also return multipart data for a do request, although YNL
|
|
14
|
+
# cannot declare such a reply. Reply mode handles this kernel behavior by
|
|
15
|
+
# retaining the first data message, draining the remainder, and completing
|
|
16
|
+
# on NLMSG_DONE instead of ACK.
|
|
8
17
|
class Exchange
|
|
9
18
|
Item = Data.define(:value)
|
|
10
19
|
Complete = Data.define
|
|
11
20
|
COMPLETE = Complete.new
|
|
12
21
|
Failure = Data.define(:exception)
|
|
13
22
|
|
|
14
|
-
|
|
23
|
+
MODES = %i[dump no_reply reply].freeze
|
|
24
|
+
private_constant :MODES
|
|
25
|
+
|
|
26
|
+
attr_reader :mode
|
|
15
27
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
28
|
+
# @rbs (mode: :dump | :no_reply | :reply) -> void
|
|
29
|
+
def initialize(mode:)
|
|
30
|
+
raise ArgumentError, "unknown exchange mode: #{mode.inspect}" unless MODES.include?(mode)
|
|
31
|
+
|
|
32
|
+
@mode = mode
|
|
19
33
|
@reply = nil
|
|
20
|
-
|
|
34
|
+
# Receive states:
|
|
35
|
+
# - :dump accepts and emits every data message, then completes on DONE.
|
|
36
|
+
# - :no_reply rejects data and completes on ACK.
|
|
37
|
+
# - :initial waits for the first data message of a reply operation.
|
|
38
|
+
# - :single retains that message and completes once ACK is received.
|
|
39
|
+
# - :multi retains the first message, drains the rest, and
|
|
40
|
+
# completes on DONE. Every data message must carry NLM_F_MULTI.
|
|
41
|
+
@state = mode == :reply ? :initial : mode
|
|
21
42
|
@acked = false
|
|
22
43
|
@cancelled = false
|
|
23
44
|
@complete = false
|
|
@@ -30,19 +51,19 @@ module Nl
|
|
|
30
51
|
return if @complete
|
|
31
52
|
|
|
32
53
|
case frame
|
|
33
|
-
when
|
|
54
|
+
when Raw::UnknownFrame
|
|
34
55
|
nil
|
|
35
|
-
when
|
|
56
|
+
when Raw::ErrorFrame
|
|
36
57
|
@cancelled ? complete(nil) : fail_with(SystemCallError.new(frame.errno))
|
|
37
|
-
when
|
|
58
|
+
when Raw::DoneFrame
|
|
38
59
|
if frame.errno && !@cancelled
|
|
39
60
|
fail_with(SystemCallError.new(frame.errno))
|
|
40
61
|
else
|
|
41
62
|
complete(@reply)
|
|
42
63
|
end
|
|
43
|
-
when
|
|
64
|
+
when Raw::AckFrame
|
|
44
65
|
accept_ack
|
|
45
|
-
when
|
|
66
|
+
when Raw::DataFrame
|
|
46
67
|
accept_reply(frame)
|
|
47
68
|
else
|
|
48
69
|
raise ArgumentError, "unexpected exchange input: #{frame.inspect}"
|
|
@@ -58,11 +79,10 @@ module Nl
|
|
|
58
79
|
def cancelled? = @mutex.synchronize { @cancelled }
|
|
59
80
|
def complete? = @mutex.synchronize { @complete }
|
|
60
81
|
def result = @mutex.synchronize { @result }
|
|
61
|
-
def expects_reply? = @expects_reply
|
|
62
82
|
|
|
63
83
|
private def accept_ack
|
|
64
84
|
@acked = true
|
|
65
|
-
if @cancelled ||
|
|
85
|
+
if @cancelled || @state == :no_reply || @state == :single
|
|
66
86
|
complete(@reply)
|
|
67
87
|
end
|
|
68
88
|
end
|
|
@@ -70,8 +90,12 @@ module Nl
|
|
|
70
90
|
private def accept_reply(frame)
|
|
71
91
|
return if @cancelled
|
|
72
92
|
|
|
73
|
-
multipart = (frame.header.flags.to_i &
|
|
93
|
+
multipart = (frame.header.flags.to_i & Raw::NLM_F_MULTI) != 0
|
|
74
94
|
case @state
|
|
95
|
+
when :dump
|
|
96
|
+
return Item.new(frame.message)
|
|
97
|
+
when :no_reply
|
|
98
|
+
return fail_with(ProtocolViolation.new('unexpected data message in a no-reply Netlink response'))
|
|
75
99
|
when :initial
|
|
76
100
|
@state = multipart ? :multi : :single
|
|
77
101
|
when :single
|
|
@@ -82,8 +106,6 @@ module Nl
|
|
|
82
106
|
end
|
|
83
107
|
end
|
|
84
108
|
|
|
85
|
-
return Item.new(frame.message) if @kind == :dump
|
|
86
|
-
|
|
87
109
|
@reply ||= frame.message
|
|
88
110
|
complete(frame.message) if @acked && @state == :single
|
|
89
111
|
end
|