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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/Rakefile +7 -0
  4. data/lib/nl/async/dispatcher.rb +16 -16
  5. data/lib/nl/async/driver.rb +0 -2
  6. data/lib/nl/async/mailbox.rb +0 -2
  7. data/lib/nl/async/operation.rb +0 -2
  8. data/lib/nl/async.rb +0 -2
  9. data/lib/nl/attribute_set.rb +173 -0
  10. data/lib/nl/bitfield32.rb +86 -0
  11. data/lib/nl/blocking_transport.rb +12 -12
  12. data/lib/nl/connection.rb +7 -9
  13. data/lib/nl/datagram.rb +7 -9
  14. data/lib/nl/datatypes.rb +454 -0
  15. data/lib/nl/decoder.rb +4 -0
  16. data/lib/nl/endian.rb +9 -0
  17. data/lib/nl/exchange.rb +40 -18
  18. data/lib/nl/family.rb +50 -55
  19. data/lib/nl/genl/client.rb +105 -0
  20. data/lib/nl/genl/protocol.rb +59 -0
  21. data/lib/nl/genl/wire.rb +95 -0
  22. data/lib/nl/genl.rb +60 -62
  23. data/lib/nl/notification.rb +3 -6
  24. data/lib/nl/notification_router.rb +30 -23
  25. data/lib/nl/raw/client.rb +83 -0
  26. data/lib/nl/raw/protocol.rb +112 -0
  27. data/lib/nl/raw/wire.rb +147 -0
  28. data/lib/nl/raw.rb +104 -0
  29. data/lib/nl/sequence_allocator.rb +0 -2
  30. data/lib/nl/socket.rb +21 -5
  31. data/lib/nl/structured_payload.rb +61 -0
  32. data/lib/nl/sub_message.rb +94 -0
  33. data/lib/nl/version.rb +1 -1
  34. data/lib/nl.rb +5 -6
  35. data/sig/generated/nl/async/dispatcher.rbs +53 -0
  36. data/sig/generated/nl/async/driver.rbs +21 -0
  37. data/sig/generated/nl/async/mailbox.rbs +32 -0
  38. data/sig/generated/nl/async/operation.rbs +123 -0
  39. data/sig/generated/nl/async.rbs +9 -0
  40. data/sig/generated/nl/attribute_set.rbs +50 -0
  41. data/sig/generated/nl/bitfield32.rbs +41 -0
  42. data/sig/generated/nl/blocking_transport.rbs +33 -0
  43. data/sig/generated/nl/connection.rbs +26 -0
  44. data/sig/generated/nl/datagram.rbs +11 -0
  45. data/sig/generated/nl/datatypes.rbs +181 -0
  46. data/sig/generated/nl/decoder.rbs +38 -0
  47. data/sig/generated/nl/encoder.rbs +29 -0
  48. data/sig/generated/nl/endian.rbs +29 -0
  49. data/sig/generated/nl/error.rbs +27 -0
  50. data/sig/generated/nl/exchange.rbs +75 -0
  51. data/sig/generated/nl/family.rbs +89 -0
  52. data/sig/generated/nl/genl/client.rbs +74 -0
  53. data/sig/generated/nl/genl/protocol.rbs +37 -0
  54. data/sig/generated/nl/genl/wire.rbs +113 -0
  55. data/sig/generated/nl/genl.rbs +50 -0
  56. data/sig/generated/nl/notification.rbs +53 -0
  57. data/sig/generated/nl/notification_router.rbs +30 -0
  58. data/sig/generated/nl/raw/client.rbs +49 -0
  59. data/sig/generated/nl/raw/protocol.rbs +122 -0
  60. data/sig/generated/nl/raw/wire.rbs +151 -0
  61. data/sig/generated/nl/raw.rbs +70 -0
  62. data/sig/generated/nl/sequence_allocator.rbs +15 -0
  63. data/sig/generated/nl/socket.rbs +59 -0
  64. data/sig/generated/nl/structured_payload.rbs +25 -0
  65. data/sig/generated/nl/sub_message.rbs +74 -0
  66. data/sig/generated/nl/version.rbs +5 -0
  67. data/sig/generated/nl.rbs +2 -0
  68. metadata +48 -7
  69. data/lib/nl/core.rb +0 -94
  70. data/lib/nl/genl/connection.rb +0 -89
  71. data/lib/nl/protocols/genl.rb +0 -74
  72. data/lib/nl/protocols/raw.rb +0 -460
@@ -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
@@ -18,6 +18,10 @@ module Nl
18
18
  @position + size <= @limit
19
19
  end
20
20
 
21
+ def remaining
22
+ @limit - @position
23
+ end
24
+
21
25
  def limit(size)
22
26
  orig_limit = @limit
23
27
  @limit = @position + size
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 'protocols/raw'
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
- attr_reader :kind
23
+ MODES = %i[dump no_reply reply].freeze
24
+ private_constant :MODES
25
+
26
+ attr_reader :mode
15
27
 
16
- def initialize(kind:, expects_reply:)
17
- @kind = kind
18
- @expects_reply = expects_reply
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
- @state = kind == :dump ? :multi : :initial
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 Protocols::Raw::UnknownFrame
54
+ when Raw::UnknownFrame
34
55
  nil
35
- when Protocols::Raw::ErrorFrame
56
+ when Raw::ErrorFrame
36
57
  @cancelled ? complete(nil) : fail_with(SystemCallError.new(frame.errno))
37
- when Protocols::Raw::DoneFrame
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 Protocols::Raw::AckFrame
64
+ when Raw::AckFrame
44
65
  accept_ack
45
- when Protocols::Raw::DataFrame
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 || !@expects_reply || @state == :single
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 & Core::NLM_F_MULTI) != 0
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