nl 0.3.0 → 0.4.1

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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -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 +10 -0
  17. data/lib/nl/exchange.rb +40 -18
  18. data/lib/nl/family.rb +18 -41
  19. data/lib/nl/genl/client.rb +104 -0
  20. data/lib/nl/genl/protocol.rb +59 -0
  21. data/lib/nl/genl/wire.rb +94 -0
  22. data/lib/nl/genl.rb +61 -61
  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 +146 -0
  28. data/lib/nl/raw.rb +104 -0
  29. data/lib/nl/sequence_allocator.rb +0 -2
  30. data/lib/nl/structured_payload.rb +61 -0
  31. data/lib/nl/sub_message.rb +94 -0
  32. data/lib/nl/version.rb +1 -1
  33. data/lib/nl.rb +5 -6
  34. data/sig/generated/nl/async/dispatcher.rbs +53 -0
  35. data/sig/generated/nl/async/driver.rbs +21 -0
  36. data/sig/generated/nl/async/mailbox.rbs +32 -0
  37. data/sig/generated/nl/async/operation.rbs +123 -0
  38. data/sig/generated/nl/async.rbs +9 -0
  39. data/sig/generated/nl/attribute_set.rbs +50 -0
  40. data/sig/generated/nl/bitfield32.rbs +41 -0
  41. data/sig/generated/nl/blocking_transport.rbs +33 -0
  42. data/sig/generated/nl/connection.rbs +26 -0
  43. data/sig/generated/nl/datagram.rbs +11 -0
  44. data/sig/generated/nl/datatypes.rbs +181 -0
  45. data/sig/generated/nl/decoder.rbs +38 -0
  46. data/sig/generated/nl/encoder.rbs +29 -0
  47. data/sig/generated/nl/endian.rbs +28 -0
  48. data/sig/generated/nl/error.rbs +27 -0
  49. data/sig/generated/nl/exchange.rbs +75 -0
  50. data/sig/generated/nl/family.rbs +71 -0
  51. data/sig/generated/nl/genl/client.rbs +74 -0
  52. data/sig/generated/nl/genl/protocol.rbs +37 -0
  53. data/sig/generated/nl/genl/wire.rbs +113 -0
  54. data/sig/generated/nl/genl.rbs +50 -0
  55. data/sig/generated/nl/notification.rbs +53 -0
  56. data/sig/generated/nl/notification_router.rbs +30 -0
  57. data/sig/generated/nl/raw/client.rbs +50 -0
  58. data/sig/generated/nl/raw/protocol.rbs +122 -0
  59. data/sig/generated/nl/raw/wire.rbs +151 -0
  60. data/sig/generated/nl/raw.rbs +70 -0
  61. data/sig/generated/nl/sequence_allocator.rbs +15 -0
  62. data/sig/generated/nl/socket.rbs +40 -0
  63. data/sig/generated/nl/structured_payload.rbs +25 -0
  64. data/sig/generated/nl/sub_message.rbs +74 -0
  65. data/sig/generated/nl/version.rbs +5 -0
  66. data/sig/generated/nl.rbs +2 -0
  67. metadata +48 -7
  68. data/lib/nl/core.rb +0 -94
  69. data/lib/nl/genl/connection.rb +0 -89
  70. data/lib/nl/protocols/genl.rb +0 -74
  71. data/lib/nl/protocols/raw.rb +0 -460
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 06d7f10231a9e4b6ce47149f17099f6c72eaad8c72101ac768d3413ee1a8b12b
4
- data.tar.gz: 9ecf3d32020db7cebc41de402a6e47b524aa3146db8d935cdba467d31faeda6d
3
+ metadata.gz: 8fbd6064a908ddacbadbb65eaa2f7c7ab862baf91478804f1915ba988271b759
4
+ data.tar.gz: f49043a50d47c82da9f1f818f02948a12f386df091b7041e4ae1b7d755780b23
5
5
  SHA512:
6
- metadata.gz: 4ec97708c16968c722830441a0755419ebb38e5740c11a996d4ea7e6f9f07dec1803877159eb248096b069c4c1808398777fd13aff5c6aa92a8dafc47d901bf7
7
- data.tar.gz: 112cfdc2829d1e6f135995315cf1fa3ef3b3aa81e1ba5ac47da3011e029113bb04a6a6c2e4a369346a38cb08a8735bb021d52b1eba0a0d540d31559f6337e71c
6
+ metadata.gz: 0f162b7c19214639fdd277bea5fdedb1ef4661a8747de0458b08dd85fefa94ec36dfbbd71b039ef5143069ca903e54d133029528dd4a75cee865d6b409db7251
7
+ data.tar.gz: c538d2a693926047576fc49bd82fd0bf435cbe19e3c61c21536826a0d873f260b87f2b6544dec4dac73e5385c11baa1da741c901bd81f2711eadb827cf34f356
data/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## v0.4.1 (2026-09-08)
6
+
7
+ ## v0.4.0 (2026-09-08)
8
+
9
+ - Represent `bitfield32` values with mutable `Bitfield32` objects.
10
+ - Support fixed-length binary values and structured binary values.
11
+ - Encode Generic Netlink requests with the protocol version.
12
+ - Support building and reading attributes declared as repeatable.
13
+ - Add `Raw::Client` for using raw families with the same protocol number over one connection.
14
+ - Rename `Genl::Connection` to `Genl::Client`.
15
+ - Remove `UnknownNotification`; unsolicited messages without a registered decoder are now ignored.
16
+ - Add context-aware encoding and decoding support for polymorphic sub-messages.
17
+
5
18
  ## v0.3.0 (2026-09-01)
6
19
 
7
20
  - Fix encoding of nested attributes and mark them with `NLA_F_NESTED`.
data/Rakefile CHANGED
@@ -2,4 +2,11 @@ require 'rspec/core/rake_task'
2
2
 
3
3
  RSpec::Core::RakeTask.new(:spec)
4
4
 
5
+ desc 'Generate RBS signatures from inline annotations'
6
+ task :rbs do
7
+ sh '../bin/rbs-inline', '--output=sig/generated', '--opt-out', 'lib'
8
+ end
9
+
10
+ task :build => :rbs
11
+
5
12
  task default: :spec
@@ -1,5 +1,3 @@
1
- # rbs_inline: enabled
2
-
3
1
  require_relative '../datagram'
4
2
  require_relative '../error'
5
3
  require_relative '../exchange'
@@ -10,11 +8,12 @@ module Nl
10
8
  module Async
11
9
  # Owns the single receive loop for a socket and routes replies by sequence.
12
10
  class Dispatcher
13
- Pending = Data.define(:exchange, :protocol, :reply_class, :sink)
11
+ Pending = Data.define(:exchange, :endpoint, :reply_class, :sink)
14
12
  private_constant :Pending
15
13
 
16
- def initialize(socket, executor: :thread, notifications:)
14
+ def initialize(socket, protocol:, executor: :thread, notifications:)
17
15
  @socket = socket
16
+ @protocol = protocol
18
17
  @sequences = SequenceAllocator.new
19
18
  @notifications = notifications
20
19
  @driver = executor.respond_to?(:start) ? executor : Async.driver(executor)
@@ -25,8 +24,8 @@ module Nl
25
24
  @task = @driver.start { receive_loop }
26
25
  end
27
26
 
28
- def exchange(protocol, kind, request_class, reply_class, args, &block)
29
- operation = exchange_async(protocol, kind, request_class, reply_class, args)
27
+ def exchange(endpoint, kind, request_class, reply_class, args, &block)
28
+ operation = exchange_async(endpoint, kind, request_class, reply_class, args)
30
29
  if kind == :dump
31
30
  if block
32
31
  operation.each(&block)
@@ -39,8 +38,8 @@ module Nl
39
38
  end
40
39
  end
41
40
 
42
- def exchange_async(protocol, kind, request_class, reply_class, args, stream_capacity: nil)
43
- request = protocol.build_request(kind, request_class, args)
41
+ def exchange_async(endpoint, kind, request_class, reply_class, args, stream_capacity: nil)
42
+ request = @protocol.build_request(endpoint, kind, request_class, args)
44
43
 
45
44
  key = nil
46
45
  seq = pid = nil
@@ -58,12 +57,13 @@ module Nl
58
57
  else
59
58
  Future.build(mailbox:, on_close: -> { discard(key) })
60
59
  end
61
- exchange = Exchange.new(kind:, expects_reply: !reply_class.nil?)
62
- @pending[key] = Pending.new(exchange:, protocol:, reply_class:, sink:)
60
+ mode = kind == :dump ? :dump : (reply_class ? :reply : :no_reply)
61
+ exchange = Exchange.new(mode:)
62
+ @pending[key] = Pending.new(exchange:, endpoint:, reply_class:, sink:)
63
63
  end
64
64
 
65
65
  begin
66
- protocol.send_message(@socket, request, seq:, pid:)
66
+ @protocol.send_message(@socket, endpoint, request, seq:, pid:)
67
67
  rescue Exception => error
68
68
  fail_pending(key, error)
69
69
  raise
@@ -76,8 +76,8 @@ module Nl
76
76
  true
77
77
  end
78
78
 
79
- def receive_notification(protocol, timeout: nil)
80
- @notifications.channel(protocol).pop(timeout:)
79
+ def receive_notification(endpoint, timeout: nil)
80
+ @notifications.channel(endpoint).pop(timeout:)
81
81
  end
82
82
 
83
83
  def close
@@ -134,7 +134,7 @@ module Nl
134
134
  end
135
135
 
136
136
  begin
137
- message = pending.protocol.decode_frame(header, payload, pending.reply_class)
137
+ message = @protocol.decode_frame(pending.endpoint, header, payload, pending.reply_class)
138
138
  dispatch_outcome(key, pending, pending.exchange.accept(message))
139
139
  rescue Exception => error
140
140
  fail_pending(key, error) if pending
@@ -164,9 +164,9 @@ module Nl
164
164
  return unless removed
165
165
  return if pending.exchange.cancelled?
166
166
 
167
- if pending.exchange.kind == :dump
167
+ if pending.exchange.mode == :dump
168
168
  pending.sink.finish
169
- elsif pending.exchange.expects_reply?
169
+ elsif pending.exchange.mode == :reply
170
170
  pending.sink.succeed(value)
171
171
  else
172
172
  pending.sink.finish
@@ -1,5 +1,3 @@
1
- # rbs_inline: enabled
2
-
3
1
  module Nl
4
2
  module Async
5
3
  module Drivers
@@ -1,5 +1,3 @@
1
- # rbs_inline: enabled
2
-
3
1
  require_relative '../error'
4
2
 
5
3
  module Nl
@@ -1,5 +1,3 @@
1
- # rbs_inline: enabled
2
-
3
1
  require_relative '../error'
4
2
 
5
3
  module Nl
data/lib/nl/async.rb CHANGED
@@ -1,5 +1,3 @@
1
- # rbs_inline: enabled
2
-
3
1
  require_relative 'error'
4
2
 
5
3
  module Nl
@@ -0,0 +1,173 @@
1
+ require_relative 'datatypes'
2
+
3
+ module Nl
4
+ class AttributeSet
5
+ Attribute = Struct.new(:value)
6
+ class Attribute
7
+ MULTI = false
8
+ ORDER = nil
9
+ SELECTOR_SLOT = nil
10
+
11
+ def self.multi?
12
+ self::MULTI
13
+ end
14
+
15
+ def self.decode(decoder, context: nil, nlattr_type_flags: 0)
16
+ new(self::DATATYPE.decode(decoder, context:, nlattr_type_flags:))
17
+ end
18
+
19
+ def encode(encoder, context: nil)
20
+ self.class::DATATYPE.encode(encoder, self.value, context:)
21
+ end
22
+ end
23
+
24
+ def initialize(attributes)
25
+ attr_class = self.class::Attribute
26
+
27
+ attributes.each do |attr|
28
+ unless attr.kind_of?(attr_class)
29
+ raise TypeError, "attribute must be an instance of #{attr_class}"
30
+ end
31
+ end
32
+
33
+ @attributes = Array(attributes)
34
+ end
35
+
36
+ def [](type)
37
+ case type
38
+ when Symbol
39
+ attr_class = self.class.by_name(type)
40
+ when Integer
41
+ attr_class = self.class.by_type(type)
42
+ else
43
+ raise TypeError, "attribute type must be a Symbol or an Integer"
44
+ end
45
+
46
+ attributes = @attributes.select { it.kind_of?(attr_class) }
47
+ attr_class.multi? ? attributes : attributes.first
48
+ end
49
+
50
+ def <<(attr)
51
+ attr_class = self.class::Attribute
52
+ unless attr.kind_of?(attr_class)
53
+ raise TypeError, "attribute must be an instance of #{attr_class}"
54
+ end
55
+
56
+ @attributes << attr
57
+ end
58
+
59
+ SELECTOR_NAMES = {local: [].freeze, external: [].freeze}.freeze
60
+
61
+ private def encode1(encoder, attr, context)
62
+ datatype = attr.class::DATATYPE
63
+ flags = datatype.nlattr_type_flags(attr.value, context:)
64
+ type = attr.class::TYPE | flags
65
+ nlattr = Raw::NlAttr.new(0, type)
66
+ encoder.measure(Endian::Host::U16) do
67
+ nlattr.encode(encoder)
68
+ attr.encode(encoder, context:)
69
+ end
70
+ encoder.align_to(Raw::NLA_ALIGNTO)
71
+ end
72
+
73
+ def encode(encoder, external_selectors: [])
74
+ context = Selector::State.new(self.class::SELECTOR_NAMES.fetch(:local).length, external_selectors)
75
+ @attributes.each do |attr|
76
+ if slot = attr.class::SELECTOR_SLOT
77
+ context.set_local(slot, attr.value)
78
+ end
79
+ end
80
+
81
+ attributes = @attributes.sort_by { it.class::ORDER }
82
+ attributes.each do |attr|
83
+ encode1(encoder, attr, context)
84
+ end
85
+ rescue Selector::MissingSelectorValueError => error
86
+ raise ArgumentError, "selector #{selector_name(error).inspect} is required by a dependent attribute"
87
+ rescue Selector::UnknownSelectorValueError => error
88
+ raise ArgumentError, "unknown sub-message selector value: #{error.value.inspect}"
89
+ end
90
+
91
+ private def selector_name(error)
92
+ self.class::SELECTOR_NAMES.fetch(error.scope).fetch(error.index)
93
+ end
94
+
95
+ class << self
96
+ private def decode1(decoder, context)
97
+ nlattr = Raw::NlAttr.decode(decoder)
98
+ flags = nlattr.type & (Raw::NLA_F_NESTED | Raw::NLA_F_NET_BYTEORDER)
99
+ attr = decoder.limit(nlattr.len - Raw::NLA_HDRLEN) do
100
+ if attr_class = self::BY_TYPE[nlattr.type & Raw::NLA_TYPE_MASK]
101
+ attr_class.decode(decoder, context:, nlattr_type_flags: flags)
102
+ else
103
+ decoder.skip
104
+ nil
105
+ end
106
+ end
107
+ decoder.align_to(Raw::NLA_ALIGNTO)
108
+ if attr && (slot = attr.class::SELECTOR_SLOT)
109
+ context.set_local(slot, attr.value)
110
+ end
111
+ attr
112
+ end
113
+
114
+ def decode(decoder, external_selectors: [])
115
+ context = Selector::State.new(self::SELECTOR_NAMES.fetch(:local).length, external_selectors)
116
+ attrs = []
117
+ while decoder.available?
118
+ attr = decode1(decoder, context)
119
+ attrs << attr
120
+ end
121
+ new(attrs.compact)
122
+ rescue Selector::MissingSelectorValueError => error
123
+ raise Decoder::Error,
124
+ "selector #{selector_name(error).inspect} must precede the dependent attribute"
125
+ rescue Selector::UnknownSelectorValueError => error
126
+ raise Decoder::Error, "unknown sub-message selector value: #{error.value.inspect}"
127
+ end
128
+
129
+ def build_attributes(params = nil, external_selectors: [], **keywords)
130
+ params = (params || {}).merge(keywords)
131
+ unknown = params.keys - self::BY_NAME.keys
132
+ raise ArgumentError, "unknown attributes: #{unknown.join(', ')}" unless unknown.empty?
133
+
134
+ context = Selector::State.new(self::SELECTOR_NAMES.fetch(:local).length, external_selectors)
135
+ coerced_selectors = {}
136
+ self::SELECTOR_NAMES.fetch(:local).each_with_index do |name, slot|
137
+ next unless params.key?(name)
138
+ attr_class = self::BY_NAME.fetch(name)
139
+ value = attr_class::DATATYPE.coerce(params.fetch(name))
140
+ coerced_selectors[name] = value
141
+ context.set_local(slot, value)
142
+ end
143
+
144
+ attrs = params.sort_by { |name, _| self::BY_NAME.fetch(name)::ORDER }.flat_map do |name, value|
145
+ attr_class = self::BY_NAME[name] or raise "Unknown attribute #{name}"
146
+ if attr_class.multi?
147
+ unless value.is_a?(Array)
148
+ raise TypeError, "value for multi-attribute #{name} must be an Array"
149
+ end
150
+ value.map { attr_class.new(coerce(attr_class::DATATYPE, it, context)) }
151
+ else
152
+ value = coerced_selectors.fetch(name) { coerce(attr_class::DATATYPE, value, context) }
153
+ attr_class.new(value)
154
+ end
155
+ end
156
+ new(attrs)
157
+ rescue Selector::MissingSelectorValueError => error
158
+ raise ArgumentError, "selector #{selector_name(error).inspect} is required by a dependent attribute"
159
+ rescue Selector::UnknownSelectorValueError => error
160
+ raise ArgumentError, "unknown sub-message selector value: #{error.value.inspect}"
161
+ end
162
+
163
+ private def coerce(datatype, value, context)
164
+ datatype.coerce(value, context:)
165
+ end
166
+
167
+ private def selector_name(error)
168
+ self::SELECTOR_NAMES.fetch(error.scope).fetch(error.index)
169
+ end
170
+
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,86 @@
1
+ module Nl
2
+ # A 32-bit value paired with a mask selecting the meaningful bits.
3
+ class Bitfield32
4
+ UINT32_RANGE = (0...2**32)
5
+
6
+ # A 32-bit flags
7
+ # @return [Integer]
8
+ attr_reader :value #: Integer
9
+
10
+ # A 32-bit mask selecting the meaningful bits
11
+ # @return [Integer]
12
+ attr_reader :selector #: Integer
13
+
14
+ # @param [Integer] value
15
+ # @param [Integer] selector
16
+ # @rbs (Integer value, Integer selector) -> void
17
+ def initialize(value, selector)
18
+ validate_uint32(value, :value)
19
+ validate_uint32(selector, :selector)
20
+ if value & ~selector != 0
21
+ raise ArgumentError, 'value contains bits which are not selected'
22
+ end
23
+
24
+ @value = value
25
+ @selector = selector
26
+ end
27
+
28
+ # Returns 1 or 0 for a selected bit, or nil for an unselected bit.
29
+ # @param [Integer] index Bits are indexed from the least significant bit, starting at zero.
30
+ # @return [0, 1, nil]
31
+ # @rbs (Integer index) -> (0 | 1 | nil)
32
+ def [](index)
33
+ mask = bit_mask(index)
34
+ return nil if selector & mask == 0
35
+
36
+ value & mask == 0 ? 0 : 1
37
+ end
38
+
39
+ # Selects and sets or clears a bit. Assigning nil unselects it.
40
+ # @param [Integer] index Bits are indexed from the least significant bit, starting at zero.
41
+ # @param [0, 1, nil] state
42
+ # @rbs (Integer index, (0 | 1 | nil) state) -> (0 | 1 | nil)
43
+ def []=(index, state)
44
+ mask = bit_mask(index)
45
+ case state
46
+ when 1
47
+ @selector |= mask
48
+ @value |= mask
49
+ when 0
50
+ @selector |= mask
51
+ @value &= ~mask
52
+ when nil
53
+ @selector &= ~mask
54
+ @value &= ~mask
55
+ else
56
+ raise TypeError, 'bit state must be 0, 1, or nil'
57
+ end
58
+
59
+ state
60
+ end
61
+
62
+ # Converts a bit index to a bit mask
63
+ # @rbs (Integer index) -> Integer
64
+ private def bit_mask(index)
65
+ unless index.is_a?(Integer)
66
+ raise TypeError, 'bit index must be an Integer'
67
+ end
68
+ unless (0...32).cover?(index)
69
+ raise RangeError, "bit index #{index.inspect} is outside the 0...32 range"
70
+ end
71
+
72
+ 1 << index
73
+ end
74
+
75
+ # Validates if an Integer fits in uint32
76
+ # @rbs (Integer integer, Symbol name) -> void
77
+ private def validate_uint32(integer, name)
78
+ unless integer.is_a?(Integer)
79
+ raise TypeError, "#{name} must be an Integer"
80
+ end
81
+ unless UINT32_RANGE.cover?(integer)
82
+ raise RangeError, "#{name} #{integer.inspect} is outside the #{UINT32_RANGE} range"
83
+ end
84
+ end
85
+ end
86
+ end
@@ -1,5 +1,3 @@
1
- # rbs_inline: enabled
2
-
3
1
  require_relative 'datagram'
4
2
  require_relative 'exchange'
5
3
  require_relative 'notification_router'
@@ -20,28 +18,30 @@ module Nl
20
18
  end
21
19
  end
22
20
 
23
- def initialize(socket, notifications:)
21
+ def initialize(socket, protocol:, notifications:)
24
22
  @socket = socket
23
+ @protocol = protocol
25
24
  @sequences = SequenceAllocator.new
26
25
  @mutex = Mutex.new
27
26
  @notifications = notifications
28
27
  end
29
28
 
30
- def exchange(protocol, kind, request_class, reply_class, args)
29
+ def exchange(endpoint, kind, request_class, reply_class, args)
31
30
  unless locked = @mutex.try_lock
32
31
  raise ConcurrentOperationError, 'BlockingTransport supports only one active operation'
33
32
  end
34
33
 
35
- request = protocol.build_request(kind, request_class, args)
34
+ request = @protocol.build_request(endpoint, kind, request_class, args)
36
35
  seq = @sequences.next
37
36
  pid = @socket.local_port_id
38
37
  key = [seq, pid]
39
- protocol.send_message(@socket, request, seq:, pid:)
40
- exchange = Exchange.new(kind:, expects_reply: !reply_class.nil?)
38
+ @protocol.send_message(@socket, endpoint, request, seq:, pid:)
39
+ mode = kind == :dump ? :dump : (reply_class ? :reply : :no_reply)
40
+ exchange = Exchange.new(mode:)
41
41
  result = [] unless block_given?
42
42
 
43
43
  until exchange.complete?
44
- receive(protocol, key, reply_class) do |message|
44
+ receive(endpoint, key, reply_class) do |message|
45
45
  case outcome = exchange.accept(message)
46
46
  when Exchange::Item
47
47
  block_given? ? yield(outcome.value) : result << outcome.value
@@ -62,8 +62,8 @@ module Nl
62
62
  false
63
63
  end
64
64
 
65
- def receive_notification(protocol, timeout: nil)
66
- channel = @notifications.channel(protocol)
65
+ def receive_notification(endpoint, timeout: nil)
66
+ channel = @notifications.channel(endpoint)
67
67
  return channel.pop(timeout: 0)
68
68
  rescue TimeoutError
69
69
  unless locked = @mutex.try_lock
@@ -95,11 +95,11 @@ module Nl
95
95
  nil
96
96
  end
97
97
 
98
- private def receive(protocol, expected_key, reply_class)
98
+ private def receive(endpoint, expected_key, reply_class)
99
99
  Datagram.each_frame(receive_datagram) do |header, payload|
100
100
  actual_key = [header.seq, header.pid]
101
101
  if actual_key == expected_key
102
- yield protocol.decode_frame(header, payload, reply_class)
102
+ yield @protocol.decode_frame(endpoint, header, payload, reply_class)
103
103
  elsif header.seq.zero?
104
104
  @notifications.route(header, payload)
105
105
  else
data/lib/nl/connection.rb CHANGED
@@ -1,5 +1,3 @@
1
- # rbs_inline: enabled
2
-
3
1
  require_relative 'async'
4
2
  require_relative 'blocking_transport'
5
3
  require_relative 'notification_router'
@@ -14,16 +12,16 @@ module Nl
14
12
  socket = Socket.new(protocol.protonum)
15
13
  socket.bind(Socket.sockaddr_nl(0, 0))
16
14
  notifications = NotificationRouter.new(
17
- routing: protocol.notification_routing,
15
+ protocol:,
18
16
  capacity: notification_capacity,
19
17
  )
20
18
 
21
19
  @socket = socket
22
20
  @notifications = notifications
23
21
  @transport = if executor
24
- Async::Dispatcher.new(socket, executor:, notifications:)
22
+ Async::Dispatcher.new(socket, protocol:, executor:, notifications:)
25
23
  else
26
- BlockingTransport.new(socket, notifications:)
24
+ BlockingTransport.new(socket, protocol:, notifications:)
27
25
  end
28
26
  rescue Exception
29
27
  @transport ? @transport.close : socket&.close
@@ -35,8 +33,8 @@ module Nl
35
33
  def exchange_async(...) = @transport.exchange_async(...)
36
34
  def async_capable? = @transport.async_capable?
37
35
 
38
- def register_notifications(protocol, classes)
39
- @notifications.register(protocol, classes)
36
+ def register_notifications(endpoint, classes)
37
+ @notifications.register(endpoint, classes)
40
38
  end
41
39
 
42
40
  def add_memberships(group_ids)
@@ -49,8 +47,8 @@ module Nl
49
47
  nil
50
48
  end
51
49
 
52
- def receive_notification(protocol, timeout: nil)
53
- @transport.receive_notification(protocol, timeout:)
50
+ def receive_notification(endpoint, timeout: nil)
51
+ @transport.receive_notification(endpoint, timeout:)
54
52
  end
55
53
 
56
54
  def close
data/lib/nl/datagram.rb CHANGED
@@ -1,22 +1,20 @@
1
- # rbs_inline: enabled
2
-
3
- require_relative 'core'
1
+ require_relative 'raw/wire'
4
2
  require_relative 'decoder'
5
3
 
6
4
  module Nl
7
5
  # Splits Netlink datagrams into aligned header/payload frames.
8
6
  module Datagram
9
- # @rbs (IO::Buffer buffer) { (Core::NlMsgHdr, IO::Buffer) -> void } -> nil
10
- # | (IO::Buffer buffer) -> Enumerator[[Core::NlMsgHdr, IO::Buffer], nil]
7
+ # @rbs (IO::Buffer buffer) { (Raw::NlMsgHdr, IO::Buffer) -> void } -> nil
8
+ # | (IO::Buffer buffer) -> Enumerator[[Raw::NlMsgHdr, IO::Buffer], nil]
11
9
  def self.each_frame(buffer)
12
10
  return enum_for(__method__, buffer) unless block_given?
13
11
 
14
12
  decoder = Decoder.new(buffer)
15
- while decoder.available?(Core::NLMSG_HDRLEN)
16
- header = Core::NlMsgHdr.decode(decoder)
17
- payload_size = header.len - Core::NLMSG_HDRLEN
13
+ while decoder.available?(Raw::NLMSG_HDRLEN)
14
+ header = Raw::NlMsgHdr.decode(decoder)
15
+ payload_size = header.len - Raw::NLMSG_HDRLEN
18
16
  payload = decoder.get_buffer(payload_size)
19
- decoder.align_to(Core::NLMSG_ALIGNTO)
17
+ decoder.align_to(Raw::NLMSG_ALIGNTO)
20
18
  yield header, payload
21
19
  end
22
20
  end