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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -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 +10 -0
- data/lib/nl/exchange.rb +40 -18
- data/lib/nl/family.rb +18 -41
- data/lib/nl/genl/client.rb +104 -0
- data/lib/nl/genl/protocol.rb +59 -0
- data/lib/nl/genl/wire.rb +94 -0
- data/lib/nl/genl.rb +61 -61
- 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 +146 -0
- data/lib/nl/raw.rb +104 -0
- data/lib/nl/sequence_allocator.rb +0 -2
- 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 +28 -0
- data/sig/generated/nl/error.rbs +27 -0
- data/sig/generated/nl/exchange.rbs +75 -0
- data/sig/generated/nl/family.rbs +71 -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 +50 -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 +40 -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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8fbd6064a908ddacbadbb65eaa2f7c7ab862baf91478804f1915ba988271b759
|
|
4
|
+
data.tar.gz: f49043a50d47c82da9f1f818f02948a12f386df091b7041e4ae1b7d755780b23
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
data/lib/nl/async/dispatcher.rb
CHANGED
|
@@ -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, :
|
|
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(
|
|
29
|
-
operation = exchange_async(
|
|
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(
|
|
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
|
-
|
|
62
|
-
|
|
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(
|
|
80
|
-
@notifications.channel(
|
|
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 =
|
|
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.
|
|
167
|
+
if pending.exchange.mode == :dump
|
|
168
168
|
pending.sink.finish
|
|
169
|
-
elsif pending.exchange.
|
|
169
|
+
elsif pending.exchange.mode == :reply
|
|
170
170
|
pending.sink.succeed(value)
|
|
171
171
|
else
|
|
172
172
|
pending.sink.finish
|
data/lib/nl/async/driver.rb
CHANGED
data/lib/nl/async/mailbox.rb
CHANGED
data/lib/nl/async/operation.rb
CHANGED
data/lib/nl/async.rb
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
66
|
-
channel = @notifications.channel(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
39
|
-
@notifications.register(
|
|
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(
|
|
53
|
-
@transport.receive_notification(
|
|
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
|
-
|
|
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) { (
|
|
10
|
-
# | (IO::Buffer buffer) -> Enumerator[[
|
|
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?(
|
|
16
|
-
header =
|
|
17
|
-
payload_size = header.len -
|
|
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(
|
|
17
|
+
decoder.align_to(Raw::NLMSG_ALIGNTO)
|
|
20
18
|
yield header, payload
|
|
21
19
|
end
|
|
22
20
|
end
|