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
data/lib/nl/family.rb CHANGED
@@ -1,5 +1,3 @@
1
- #--
2
- # rbs_inline: enabled
3
1
  require_relative 'connection'
4
2
  require_relative 'notification'
5
3
 
@@ -9,14 +7,14 @@ module Nl
9
7
  #
10
8
  # interface _Connection
11
9
  # def exchange: (
12
- # Protocols::Raw protocol,
10
+ # Raw::Endpoint endpoint,
13
11
  # Symbol kind,
14
12
  # Class request_class,
15
13
  # Class reply_class,
16
14
  # Hash[Symbol, untyped] args
17
15
  # ) ?{ (untyped) -> void } -> untyped
18
16
  # def exchange_async: (
19
- # Protocols::Raw protocol,
17
+ # Raw::Endpoint endpoint,
20
18
  # Symbol kind,
21
19
  # Class request_class,
22
20
  # Class reply_class,
@@ -24,12 +22,16 @@ module Nl
24
22
  # ?stream_capacity: Integer?
25
23
  # ) -> (Async::Future[untyped] | Async::Stream[untyped])
26
24
  # def async_capable?: () -> bool
27
- # def register_notifications: (Protocols::Raw, Hash[Integer, Class]) -> NotificationChannel
25
+ # def register_notifications: (Raw::Endpoint, Hash[Integer, Class]) -> NotificationChannel
28
26
  # def add_memberships: (Array[Integer]) -> nil
29
27
  # def drop_memberships: (Array[Integer]) -> nil
30
- # def receive_notification: (Protocols::Raw, ?timeout: Numeric?) -> untyped
28
+ # def receive_notification: (Raw::Endpoint, ?timeout: Numeric?) -> untyped
31
29
  # def close: () -> nil
32
30
  # end
31
+ #
32
+ # interface _FamilyClass[out F]
33
+ # def new: (_Connection, endpoint: Raw::Endpoint) -> F
34
+ # end
33
35
 
34
36
  class Family
35
37
  DEFAULT_NOTIFICATION_CAPACITY = Connection::DEFAULT_NOTIFICATION_CAPACITY
@@ -42,45 +44,19 @@ module Nl
42
44
 
43
45
  #--
44
46
  # @rbs connection: _Connection
45
- # @rbs protocol: Protocol
47
+ # @rbs endpoint: Raw::Endpoint
46
48
  # @rbs return: instance
47
- def initialize(connection, protocol: self.class::PROTOCOL)
48
- @protocol = protocol
49
+ def initialize(connection, endpoint:)
50
+ @endpoint = endpoint
49
51
  @connection = connection
50
- @connection.register_notifications(@protocol, notification_classes)
52
+ @connection.register_notifications(@endpoint, notification_classes)
51
53
  @notification_stream = NotificationStream.new do |timeout|
52
- @connection.receive_notification(@protocol, timeout:)
53
- end
54
- end
55
-
56
- #--
57
- # @rbs (?executor: executor?, ?notification_capacity: Integer?) -> (Session & instance)
58
- # | [R] (?executor: executor?, ?notification_capacity: Integer?) { (instance) -> R } -> R
59
- def self.open(executor: nil, notification_capacity: DEFAULT_NOTIFICATION_CAPACITY)
60
- session = build_session(executor:, notification_capacity:)
61
- return session unless block_given?
62
-
63
- begin
64
- yield session
65
- ensure
66
- session.close
67
- end
68
- end
69
-
70
- class << self
71
- # @rbs (?executor: executor?, notification_capacity: Integer) -> (Session & instance)
72
- private def build_session(executor: nil, notification_capacity:)
73
- protocol = self::PROTOCOL
74
- connection = Connection.new(protocol:, executor:, notification_capacity:)
75
- new(connection).extend(Session)
76
- rescue Exception
77
- connection&.close
78
- raise
54
+ @connection.receive_notification(@endpoint, timeout:)
79
55
  end
80
56
  end
81
57
 
82
58
  private def exchange_message(kind, request_class, reply_class, args, &block)
83
- @connection.exchange(@protocol, kind, request_class, reply_class, args, &block)
59
+ @connection.exchange(@endpoint, kind, request_class, reply_class, args, &block)
84
60
  end
85
61
 
86
62
  def async_capable? #: bool
@@ -126,7 +102,7 @@ module Nl
126
102
  end
127
103
 
128
104
  private def exchange_message_async(kind, request_class, reply_class, args, stream_capacity: nil)
129
- @connection.exchange_async(@protocol, kind, request_class, reply_class, args, stream_capacity:)
105
+ @connection.exchange_async(@endpoint, kind, request_class, reply_class, args, stream_capacity:)
130
106
  end
131
107
 
132
108
  private def notification_classes
@@ -145,10 +121,11 @@ module Nl
145
121
  names.map do |name|
146
122
  key = name.to_sym
147
123
  group = multicast_groups.fetch(key) do
148
- raise UnknownMulticastGroupError, "unknown multicast group #{name.inspect} for #{@protocol.name}"
124
+ raise UnknownMulticastGroupError, "unknown multicast group #{name.inspect} for #{@endpoint.name}"
149
125
  end
150
- @protocol.multicast_group_id(group.name, group.id)
126
+ @endpoint.multicast_group_id(group.name, group.id)
151
127
  end
152
128
  end
153
129
  end
130
+
154
131
  end
@@ -0,0 +1,104 @@
1
+ # Generic Netlink client handling
2
+
3
+ require_relative '../connection'
4
+ require_relative 'wire'
5
+
6
+ module Nl
7
+ module Genl
8
+ # Dynamically resolved information for a generic Netlink family.
9
+ #
10
+ # @!attribute [r] id
11
+ # @return [Integer] the assigned family ID
12
+ # @!attribute [r] multicast_groups
13
+ # @return [Hash<String, Integer>] multicast group names mapped to their IDs
14
+ FamilyInfo = Data.define(
15
+ :id, #: Integer
16
+ :multicast_groups, #: Hash[String, Integer]
17
+ )
18
+
19
+ # Owns one generic Netlink connection shared by compatible families.
20
+ class Client
21
+ # Opens a generic Netlink client.
22
+ #
23
+ # @overload open(resolver:, executor: nil, notification_capacity: Nl::Connection::DEFAULT_NOTIFICATION_CAPACITY)
24
+ # The caller is responsible for closing the client.
25
+ # @param [#call] resolver a callable that resolves a family name to {FamilyInfo}
26
+ # @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
27
+ # @param [Integer] notification_capacity the maximum number of queued notifications
28
+ # @return [Client] the opened client
29
+ # @overload open(resolver:, executor: nil, notification_capacity: Nl::Connection::DEFAULT_NOTIFICATION_CAPACITY, &block)
30
+ # The client is automatically closed after the block returns.
31
+ # @param [#call] resolver a callable that resolves a family name to {FamilyInfo}
32
+ # @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
33
+ # @param [Integer] notification_capacity the maximum number of queued notifications
34
+ # @yieldparam [Client] client the opened client
35
+ # @return [Object] the value returned from the block
36
+ # @rbs (resolver: ^(instance, ::String) -> FamilyInfo, ?executor: executor?, ?notification_capacity: Integer?) -> instance
37
+ # | [R] (resolver: ^(instance, ::String) -> FamilyInfo, ?executor: executor?, ?notification_capacity: Integer?) { (instance) -> R } -> R
38
+ def self.open(resolver:, executor: nil, notification_capacity: Nl::Connection::DEFAULT_NOTIFICATION_CAPACITY)
39
+ client = new(resolver:, executor:, notification_capacity:)
40
+ return client unless block_given?
41
+
42
+ begin
43
+ yield client
44
+ ensure
45
+ client.close
46
+ end
47
+ end
48
+
49
+ # @param [#call] resolver a callable that resolves a family name to {FamilyInfo}
50
+ # @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
51
+ # @param [Integer] notification_capacity the maximum number of queued notifications
52
+ # @rbs (resolver: ^(instance, ::String) -> FamilyInfo, ?executor: executor?, ?notification_capacity: Integer?) -> void
53
+ def initialize(resolver:, executor: nil, notification_capacity: Nl::Connection::DEFAULT_NOTIFICATION_CAPACITY)
54
+ @resolver = resolver
55
+ @family_cache = {}
56
+ @family_cache_mutex = Mutex.new
57
+ @connection = Nl::Connection.new(
58
+ protocol: Protocol.new,
59
+ executor:,
60
+ notification_capacity:,
61
+ )
62
+ end
63
+
64
+ # Builds a family backed by this client's connection.
65
+ #
66
+ # @param [Class<Family>] family_class a generic Netlink family class
67
+ # @return [Family] an instance of `family_class`
68
+ # @raise [TypeError] if +family_class+ does not inherit from {Family}
69
+ # @rbs [F < Family] (_FamilyClass[F] family_class) -> F
70
+ def family(family_class)
71
+ unless family_class <= Family
72
+ raise TypeError, "family class must inherit from #{Family}"
73
+ end
74
+
75
+ info = family_info(family_class::NAME)
76
+ family_class.new(
77
+ @connection,
78
+ endpoint: Endpoint.new(family_class, info),
79
+ )
80
+ end
81
+
82
+ # Closes the underlying Netlink connection.
83
+ #
84
+ # @return [void]
85
+ # @rbs () -> void
86
+ def close
87
+ @connection.close
88
+ end
89
+
90
+ # Resolves and caches information for a family by name.
91
+ #
92
+ # @param [String] name the generic Netlink family name
93
+ # @return [FamilyInfo] the resolved family information
94
+ # @rbs (String name) -> FamilyInfo
95
+ private def family_info(name)
96
+ cached_info = @family_cache_mutex.synchronize { @family_cache[name] }
97
+ return cached_info if cached_info
98
+
99
+ info = @resolver.call(self, name)
100
+ @family_cache_mutex.synchronize { @family_cache[name] ||= info }
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,59 @@
1
+ require_relative 'wire'
2
+ require_relative '../raw'
3
+
4
+ module Nl
5
+ module Genl
6
+ # A generated Generic Netlink family bound to nlctrl-provided information.
7
+ class Endpoint < Raw::Endpoint
8
+ attr_reader :info
9
+
10
+ def initialize(definition, info)
11
+ super(definition)
12
+ @info = info
13
+ end
14
+
15
+ def family_id = @info.id
16
+ def version = @definition.version
17
+ def frame_type(_message_class) = family_id
18
+
19
+ def multicast_group_id(name, _value)
20
+ @info.multicast_groups.fetch(name) do
21
+ raise UnresolvedMulticastGroupError,
22
+ "Generic Netlink multicast group #{name.inspect} was not resolved"
23
+ end
24
+ end
25
+ end
26
+
27
+ # Socket-wide Generic Netlink wire behavior.
28
+ class Protocol < Raw::Protocol
29
+ def initialize
30
+ super(Raw::NETLINK_GENERIC)
31
+ end
32
+
33
+ def encode_message(encoder, endpoint, request, seq:, pid:)
34
+ message = request.message
35
+ header = Raw::NlMsgHdr.new(0, request.type, request.flags, seq, pid)
36
+ encoder.measure(Endian::Host::U16) do
37
+ header.encode(encoder)
38
+ Nl::Genl::GenlMsgHdr.new(message.class::TYPE, endpoint.version, 0).encode(encoder)
39
+ message.encode(encoder)
40
+ end
41
+ end
42
+
43
+ def notification_channel_key(endpoint) = endpoint.family_id
44
+ def notification_route_keys(endpoint, _classes) = [endpoint.family_id]
45
+ def notification_frame_key(header) = header.type
46
+
47
+ def notification_frame?(endpoint, header, _payload)
48
+ header.type == endpoint.family_id
49
+ end
50
+
51
+ def notification_class(endpoint, header, payload, classes)
52
+ return unless notification_frame?(endpoint, header, payload)
53
+
54
+ command = Nl::Genl::GenlMsgHdr.decode(Decoder.new(payload)).cmd
55
+ classes[command]
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,94 @@
1
+ # Generic Netlink wire definitions
2
+
3
+ require_relative '../raw/wire'
4
+ require_relative '../endian'
5
+
6
+ module Nl
7
+ module Genl
8
+ # Constants from <linux/genetlink.h>
9
+ module Constants
10
+ GENL_NAMSIZ = 16
11
+ GENL_MIN_ID = Raw::NLMSG_MIN_TYPE
12
+ GENL_MAX_ID = 1023
13
+
14
+ GENL_HDRLEN = 4
15
+
16
+ GENL_ID_GENERATE = 0
17
+ GENL_ID_CTRL = Raw::NLMSG_MIN_TYPE
18
+ GENL_ID_VFS_DQUOT = Raw::NLMSG_MIN_TYPE + 1
19
+ GENL_ID_PMCRAID = Raw::NLMSG_MIN_TYPE + 2
20
+
21
+ CTRL_CMD_UNSPEC = 0
22
+ CTRL_CMD_NEWFAMILY = 1
23
+ CTRL_CMD_DELFAMILY = 2
24
+ CTRL_CMD_GETFAMILY = 3
25
+ CTRL_CMD_NEWOPS = 4
26
+ CTRL_CMD_DELOPS = 5
27
+ CTRL_CMD_GETOPS = 6
28
+ CTRL_CMD_NEWMCAST_GRP = 7
29
+ CTRL_CMD_DELMCAST_GRP = 8
30
+ CTRL_CMD_GETMCAST_GRP = 9
31
+
32
+ CTRL_ATTR_UNSPEC = 0
33
+ CTRL_ATTR_FAMILY_ID = 1
34
+ CTRL_ATTR_FAMILY_NAME = 2
35
+ CTRL_ATTR_VERSION = 3
36
+ CTRL_ATTR_HDRSIZE = 4
37
+ CTRL_ATTR_MAXATTR = 5
38
+ CTRL_ATTR_OPS = 6
39
+ CTRL_ATTR_MCAST_GROUPS = 7
40
+
41
+ CTRL_ATTR_OP_UNSPEC = 0
42
+ CTRL_ATTR_OP_ID = 1
43
+ CTRL_ATTR_OP_FLAGS = 2
44
+
45
+ CTRL_ATTR_MCAST_GRP_UNSPEC = 0
46
+ CTRL_ATTR_MCAST_GRP_NAME = 1
47
+ CTRL_ATTR_MCAST_GRP_ID = 2
48
+ end
49
+ include Constants
50
+
51
+ # Header prepended to a Generic Netlink payload after the Netlink header.
52
+ #
53
+ # This corresponds to Linux's +struct genlmsghdr+.
54
+ #
55
+ # @!attribute [rw] cmd
56
+ # @return [Integer] family-specific command identifier
57
+ # @!attribute [rw] version
58
+ # @return [Integer] family-specific protocol version
59
+ # @!attribute [rw] reserved
60
+ # @return [Integer] reserved field, which must be zero
61
+ GenlMsgHdr = Struct.new(
62
+ :cmd, #: Integer
63
+ :version, #: Integer
64
+ :reserved, #: Integer
65
+ )
66
+
67
+ class GenlMsgHdr
68
+ FORMAT = Ractor.make_shareable([
69
+ Endian::Host::U8,
70
+ Endian::Host::U8,
71
+ Endian::Host::U16,
72
+ ])
73
+ private_constant :FORMAT
74
+
75
+ # Decodes a header from the decoder's current position.
76
+ #
77
+ # @param [Decoder] decoder the source decoder
78
+ # @return [GenlMsgHdr] the decoded header
79
+ # @rbs (Decoder decoder) -> instance
80
+ def self.decode(decoder)
81
+ new(*decoder.get_values(FORMAT))
82
+ end
83
+
84
+ # Encodes this header at the encoder's current position.
85
+ #
86
+ # @param [Encoder] encoder the destination encoder
87
+ # @return [void]
88
+ # @rbs (Encoder encoder) -> void
89
+ def encode(encoder)
90
+ encoder.put_values(FORMAT, to_a)
91
+ end
92
+ end
93
+ end
94
+ end
data/lib/nl/genl.rb CHANGED
@@ -1,74 +1,74 @@
1
- # Generic Netlink wire definitions
2
- #--
3
- # rbs_inline: enabled
1
+ # Generic Netlink family support
4
2
 
5
- require_relative 'core'
6
- require_relative 'endian'
3
+ require_relative 'genl/wire'
4
+ require_relative 'family'
5
+ require_relative 'raw'
6
+ require_relative 'genl/protocol'
7
7
 
8
8
  module Nl
9
+ # Generic Netlink families.
9
10
  module Genl
10
- module Constants
11
- # From include/uapi/linux/genetlink.
12
- GENL_NAMSIZ = 16
13
- GENL_MIN_ID = Core::NLMSG_MIN_TYPE
14
- GENL_MAX_ID = 1023
11
+ # Base class for Generic Netlink families.
12
+ class Family < Nl::Family
13
+ # Returns the family-specific protocol version.
14
+ #
15
+ # @return [Integer] the protocol version
16
+ # @rbs () -> Integer
17
+ def self.version = self::VERSION
15
18
 
16
- GENL_HDRLEN = 4
19
+ # Opens a session for this Generic Netlink family.
20
+ #
21
+ # @overload open(resolver:, executor: nil, notification_capacity: DEFAULT_NOTIFICATION_CAPACITY)
22
+ # The caller is responsible for closing the session.
23
+ # @param [#call] resolver a callable that resolves a family name to {FamilyInfo}
24
+ # @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
25
+ # @param [Integer] notification_capacity the maximum number of queued notifications
26
+ # @return [Family] the opened family session
27
+ # @overload open(resolver:, executor: nil, notification_capacity: DEFAULT_NOTIFICATION_CAPACITY, &block)
28
+ # The session is automatically closed after the block returns.
29
+ # @param [#call] resolver a callable that resolves a family name to {FamilyInfo}
30
+ # @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
31
+ # @param [Integer] notification_capacity the maximum number of queued notifications
32
+ # @yieldparam [Family] session the opened family session
33
+ # @return [Object] the value returned from the block
34
+ # @rbs (resolver: ^(Client, ::String) -> FamilyInfo, ?executor: executor?, ?notification_capacity: Integer?) -> (Nl::Family::Session & instance)
35
+ # | [R] (resolver: ^(Client, ::String) -> FamilyInfo, ?executor: executor?, ?notification_capacity: Integer?) { (instance) -> R } -> R
36
+ def self.open(resolver:, executor: nil, notification_capacity: DEFAULT_NOTIFICATION_CAPACITY)
37
+ begin
38
+ owner = Client.new(resolver:, executor:, notification_capacity:)
39
+ session = owner.family(self).extend(Nl::Family::Session)
40
+ rescue Exception
41
+ owner&.close
42
+ raise
43
+ end
44
+ return session unless block_given?
17
45
 
18
- GENL_ID_GENERATE = 0
19
- GENL_ID_CTRL = Core::NLMSG_MIN_TYPE
20
- GENL_ID_VFS_DQUOT = Core::NLMSG_MIN_TYPE + 1
21
- GENL_ID_PMCRAID = Core::NLMSG_MIN_TYPE + 2
22
-
23
- CTRL_CMD_UNSPEC = 0
24
- CTRL_CMD_NEWFAMILY = 1
25
- CTRL_CMD_DELFAMILY = 2
26
- CTRL_CMD_GETFAMILY = 3
27
- CTRL_CMD_NEWOPS = 4
28
- CTRL_CMD_DELOPS = 5
29
- CTRL_CMD_GETOPS = 6
30
- CTRL_CMD_NEWMCAST_GRP = 7
31
- CTRL_CMD_DELMCAST_GRP = 8
32
- CTRL_CMD_GETMCAST_GRP = 9
33
-
34
- CTRL_ATTR_UNSPEC = 0
35
- CTRL_ATTR_FAMILY_ID = 1
36
- CTRL_ATTR_FAMILY_NAME = 2
37
- CTRL_ATTR_VERSION = 3
38
- CTRL_ATTR_HDRSIZE = 4
39
- CTRL_ATTR_MAXATTR = 5
40
- CTRL_ATTR_OPS = 6
41
- CTRL_ATTR_MCAST_GROUPS = 7
42
-
43
- CTRL_ATTR_OP_UNSPEC = 0
44
- CTRL_ATTR_OP_ID = 1
45
- CTRL_ATTR_OP_FLAGS = 2
46
-
47
- CTRL_ATTR_MCAST_GRP_UNSPEC = 0
48
- CTRL_ATTR_MCAST_GRP_NAME = 1
49
- CTRL_ATTR_MCAST_GRP_ID = 2
50
- end
51
- include Constants
52
-
53
- GenlMsgHdr = Struct.new(:cmd, :version, :reserved)
54
- # Generic Netlink message header
55
- class GenlMsgHdr
56
- FORMAT = Ractor.make_shareable([
57
- Endian::Host::U8,
58
- Endian::Host::U8,
59
- Endian::Host::U16,
60
- ])
61
- private_constant :FORMAT
62
-
63
- def self.decode(decoder)
64
- new(*decoder.get_values(FORMAT))
46
+ begin
47
+ yield session
48
+ ensure
49
+ session.close
50
+ end
65
51
  end
52
+ end
66
53
 
67
- def encode(encoder)
68
- encoder.put_values(FORMAT, to_a)
54
+ # Base class for Generic Netlink messages.
55
+ #
56
+ # A Generic Netlink message payload begins with a {GenlMsgHdr}, followed by
57
+ # the optional fixed header and attributes handled by {Raw::Message}.
58
+ class Message < Raw::Message
59
+ # Decodes a Generic Netlink message payload.
60
+ #
61
+ # @param [Decoder] decoder the source decoder
62
+ # @param [Integer] type the family ID from the Netlink message header
63
+ # @return [Message] the decoded message
64
+ # @raise [RuntimeError] if the command does not match the message class's type
65
+ # @rbs (Decoder decoder, type: Integer) -> instance
66
+ def self.decode(decoder, type:)
67
+ genlhdr = GenlMsgHdr.decode(decoder)
68
+ super(decoder, type: genlhdr.cmd)
69
69
  end
70
70
  end
71
71
  end
72
72
  end
73
73
 
74
- require_relative 'genl/connection'
74
+ require_relative 'genl/client'
@@ -1,5 +1,3 @@
1
- # rbs_inline: enabled
2
-
3
1
  require_relative 'error'
4
2
 
5
3
  module Nl
@@ -7,9 +5,6 @@ module Nl
7
5
  # `id` is the fixed ID when the specification provides one.
8
6
  McastGroup = Data.define(:name, :id)
9
7
 
10
- # An unsolicited message whose wire type is not present in the loaded spec.
11
- UnknownNotification = Data.define(:header, :payload)
12
-
13
8
  # Thread-safe queue shared by a family's blocking and asynchronous facades.
14
9
  class NotificationChannel
15
10
  def initialize(capacity:)
@@ -96,7 +91,7 @@ module Nl
96
91
 
97
92
  # An unbounded-in-time, single-family view of unsolicited messages.
98
93
  class NotificationStream
99
- include Enumerable
94
+ include Enumerable #[untyped]
100
95
 
101
96
  def initialize(&receive)
102
97
  @receive = receive
@@ -106,6 +101,8 @@ module Nl
106
101
  @receive.call(timeout)
107
102
  end
108
103
 
104
+ # @rbs () -> Enumerator[untyped, void]
105
+ # | () { (untyped) -> void } -> void
109
106
  def each
110
107
  return enum_for(__method__) unless block_given?
111
108
 
@@ -1,64 +1,70 @@
1
- require_relative 'core'
1
+ require_relative 'raw/wire'
2
2
  require_relative 'notification'
3
3
 
4
4
  module Nl
5
5
  # Routes unsolicited frames to per-family notification channels.
6
6
  class NotificationRouter
7
- Entry = Struct.new(:protocol, :classes, :channel)
7
+ Entry = Struct.new(:endpoint, :classes, :channel)
8
8
  private_constant :Entry
9
9
 
10
- def initialize(routing:, capacity:)
11
- @routing = routing
10
+ def initialize(protocol:, capacity:)
11
+ @protocol = protocol
12
12
  @capacity = capacity
13
13
  @mutex = Mutex.new
14
14
  @entries = {}
15
+ @routes = {}
15
16
  @closed = false
16
17
  end
17
18
 
18
- def register(protocol, classes)
19
+ def register(endpoint, classes)
19
20
  @mutex.synchronize do
20
21
  raise ClosedError, 'notification router is closed' if @closed
21
22
 
22
- key = @routing.family_key(protocol)
23
- if entry = @entries[key]
24
- entry.classes.merge!(classes)
25
- else
26
- entry = Entry.new(protocol, classes.dup, NotificationChannel.new(capacity: @capacity))
27
- @entries[key] = entry
23
+ channel_key = @protocol.notification_channel_key(endpoint)
24
+ entry = @entries[channel_key]
25
+ merged_classes = entry ? entry.classes.merge(classes) : classes.dup
26
+ route_keys = @protocol.notification_route_keys(endpoint, merged_classes)
27
+ if route_key = route_keys.find { @routes[it] && !@routes[it].equal?(entry) }
28
+ raise ArgumentError, "notification route #{route_key.inspect} is already registered"
28
29
  end
30
+
31
+ unless entry
32
+ entry = Entry.new(endpoint, merged_classes, NotificationChannel.new(capacity: @capacity))
33
+ @entries[channel_key] = entry
34
+ end
35
+ entry.classes.replace(merged_classes)
36
+ route_keys.each { @routes[it] = entry }
29
37
  entry.channel
30
38
  end
31
39
  end
32
40
 
33
- def channel(protocol)
41
+ def channel(endpoint)
34
42
  @mutex.synchronize do
35
- @entries.fetch(@routing.family_key(protocol)).channel
43
+ @entries.fetch(@protocol.notification_channel_key(endpoint)).channel
36
44
  end
37
45
  end
38
46
 
39
47
  # Returns true if the frame belongs to a registered notification family.
40
48
  def route(header, payload)
41
- if header.type == Core::NLMSG_OVERRUN
49
+ if header.type == Raw::NLMSG_OVERRUN
42
50
  entries = @mutex.synchronize { @entries.values.dup }
43
51
  entries.each { it.channel.fail(NotificationLossError.new('kernel reported Netlink overrun')) }
44
52
  return true
45
53
  end
46
54
 
47
55
  entry = @mutex.synchronize do
48
- @entries[@routing.frame_key(header)]
56
+ @routes[@protocol.notification_frame_key(header)]
49
57
  end
50
58
  return false unless entry
51
- return false unless entry.protocol.notification_frame?(header, payload)
59
+ return false unless @protocol.notification_frame?(entry.endpoint, header, payload)
52
60
 
53
- message_class = entry.protocol.notification_class(header, payload, entry.classes)
54
- notification = if message_class
55
- entry.protocol.decode_notification(header, payload, message_class)
56
- else
57
- UnknownNotification.new(header:, payload: payload.get_string)
58
- end
61
+ message_class = @protocol.notification_class(entry.endpoint, header, payload, entry.classes)
62
+ return true unless message_class
63
+
64
+ notification = @protocol.decode_notification(entry.endpoint, header, payload, message_class)
59
65
  entry.channel.push(notification)
60
66
  true
61
- rescue Exception => error
67
+ rescue => error
62
68
  entry&.channel&.fail(error)
63
69
  true
64
70
  end
@@ -70,6 +76,7 @@ module Nl
70
76
  @closed = true
71
77
  old = @entries.values
72
78
  @entries.clear
79
+ @routes.clear
73
80
  old
74
81
  end
75
82
  entries.each { it.channel.close }