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
@@ -0,0 +1,83 @@
1
+ # Raw Netlink client handling
2
+
3
+ require_relative '../connection'
4
+ require_relative 'protocol'
5
+
6
+ module Nl
7
+ module Raw
8
+ # Owns one raw Netlink connection shared by compatible families.
9
+ class Client
10
+ # Opens a client for a raw Netlink protocol.
11
+ #
12
+ # @overload open(protonum:, executor: nil, notification_capacity: Nl::Connection::DEFAULT_NOTIFICATION_CAPACITY)
13
+ # The caller is responsible for closing the client.
14
+ # @param [Integer] protonum the Netlink protocol number
15
+ # @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
16
+ # @param [Integer] notification_capacity the maximum number of queued notifications
17
+ # @return [Client] the opened client
18
+ # @overload open(protonum:, executor: nil, notification_capacity: Nl::Connection::DEFAULT_NOTIFICATION_CAPACITY, &block)
19
+ # The client is automatically closed after the block returns.
20
+ # @param [Integer] protonum the Netlink protocol number
21
+ # @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
22
+ # @param [Integer] notification_capacity the maximum number of queued notifications
23
+ # @yieldparam [Client] client the opened client
24
+ # @return [Object] the value returned from the block
25
+ # @rbs (protonum: Integer, ?executor: executor?, ?notification_capacity: Integer?) -> instance
26
+ # | [R] (protonum: Integer, ?executor: executor?, ?notification_capacity: Integer?) { (instance) -> R } -> R
27
+ def self.open(protonum:, executor: nil, notification_capacity: Nl::Connection::DEFAULT_NOTIFICATION_CAPACITY)
28
+ client = new(protonum:, executor:, notification_capacity:)
29
+ return client unless block_given?
30
+
31
+ begin
32
+ yield client
33
+ ensure
34
+ client.close
35
+ end
36
+ end
37
+
38
+ # @param [Integer] protonum the Netlink protocol number
39
+ # @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
40
+ # @param [Integer] notification_capacity the maximum number of queued notifications
41
+ # @rbs (protonum: Integer, ?executor: executor?, ?notification_capacity: Integer?) -> void
42
+ def initialize(protonum:, executor: nil, notification_capacity: Nl::Connection::DEFAULT_NOTIFICATION_CAPACITY)
43
+ @protonum = protonum
44
+ @connection = Nl::Connection.new(
45
+ protocol: Protocol.new(protonum),
46
+ executor:,
47
+ notification_capacity:,
48
+ )
49
+ end
50
+
51
+ # Builds a family backed by this client's connection.
52
+ #
53
+ # @param [Class<Family>] family_class a raw Netlink family class
54
+ # @return [Family] an instance of `family_class`
55
+ # @raise [TypeError] if +family_class+ does not inherit from {Family}
56
+ # @raise [ArgumentError] if the family's protocol number differs from the client's protocol number
57
+ # @rbs [F < Family] (_FamilyClass[F] family_class) -> F
58
+ def family(family_class)
59
+ unless family_class <= Family
60
+ raise TypeError, "family class must inherit from #{Family}"
61
+ end
62
+ unless family_class::PROTONUM == @protonum
63
+ raise ArgumentError,
64
+ "family protonum #{family_class::PROTONUM} does not match client protonum #{@protonum}"
65
+ end
66
+
67
+ family_class.new(
68
+ @connection,
69
+ endpoint: Endpoint.new(family_class),
70
+ )
71
+ end
72
+
73
+ # Closes the underlying Netlink connection.
74
+ #
75
+ # @return [void]
76
+ #--
77
+ # @rbs () -> void
78
+ def close
79
+ @connection.close
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,112 @@
1
+ require_relative 'wire'
2
+ require_relative '../decoder'
3
+ require_relative '../encoder'
4
+ require_relative '../error'
5
+ require_relative '../socket'
6
+
7
+ module Nl
8
+ module Raw
9
+ AckFrame = Data.define(:header)
10
+ ErrorFrame = Data.define(:header, :errno)
11
+ DoneFrame = Data.define(:header, :errno)
12
+ UnknownFrame = Data.define(:header)
13
+ DataFrame = Data.define(:header, :message)
14
+ Request = Data.define(:type, :flags, :message)
15
+
16
+ # A generated raw Netlink family bound to its fixed wire identity.
17
+ class Endpoint
18
+ attr_reader :definition
19
+
20
+ def initialize(definition)
21
+ @definition = definition
22
+ end
23
+
24
+ def name = @definition::NAME
25
+ def frame_type(message_class) = message_class::TYPE
26
+
27
+ def multicast_group_id(name, value)
28
+ value or raise UnresolvedMulticastGroupError,
29
+ "multicast group #{name.inspect} has no fixed ID"
30
+ end
31
+ end
32
+
33
+ # Socket-wide wire behavior for a classic (netlink-raw) protocol.
34
+ class Protocol
35
+ attr_reader :protonum
36
+
37
+ def initialize(protonum)
38
+ @protonum = protonum
39
+ end
40
+
41
+ def encode_message(encoder, _endpoint, request, seq:, pid:)
42
+ header = Raw::NlMsgHdr.new(0, request.type, request.flags, seq, pid)
43
+ encoder.measure(Endian::Host::U16) do
44
+ header.encode(encoder)
45
+ request.message.encode(encoder)
46
+ end
47
+ end
48
+
49
+ # Decodes one frame using the reply class associated with its sequence.
50
+ def decode_frame(_endpoint, header, payload, message_class)
51
+ decoder = Decoder.new(payload)
52
+ if header.type < Raw::NLMSG_MIN_TYPE
53
+ case header.type
54
+ when Raw::NLMSG_ERROR
55
+ errno = decoder.get_value(Endian::Host::SINT)
56
+ if errno.positive?
57
+ raise ProtocolViolation, "expected zero or negative NLMSG_ERROR errno, got #{errno}"
58
+ end
59
+
60
+ errno.zero? ? AckFrame.new(header:) : ErrorFrame.new(header:, errno: -errno)
61
+ when Raw::NLMSG_DONE
62
+ return DoneFrame.new(header:, errno: nil) unless decoder.available?
63
+
64
+ errno = decoder.get_value(Endian::Host::SINT)
65
+ if errno.positive?
66
+ raise ProtocolViolation, "expected zero or negative NLMSG_DONE errno, got #{errno}"
67
+ end
68
+
69
+ DoneFrame.new(header:, errno: errno.negative? ? -errno : nil)
70
+ else
71
+ UnknownFrame.new(header:)
72
+ end
73
+ else
74
+ raise ArgumentError, 'reply class is required for a data message' unless message_class
75
+
76
+ DataFrame.new(header:, message: message_class.decode(decoder, type: header.type))
77
+ end
78
+ end
79
+
80
+ def send_message(socket, endpoint, request, seq:, pid:)
81
+ encoder = Encoder.new
82
+ encode_message(encoder, endpoint, request, seq:, pid:)
83
+ socket.sendmsg(encoder.buffer.get_string, 0, Socket.sockaddr_nl(0, 0))
84
+ nil
85
+ end
86
+
87
+ def build_request(endpoint, kind, request_class, args)
88
+ flags = Raw::NLM_F_REQUEST
89
+ flags |= kind == :dump ? Raw::NLM_F_DUMP : Raw::NLM_F_ACK
90
+
91
+ message = request_class.from_params(args)
92
+ Request.new(type: endpoint.frame_type(request_class), flags:, message:)
93
+ end
94
+
95
+ def notification_channel_key(endpoint) = endpoint.definition
96
+ def notification_route_keys(_endpoint, classes) = classes.keys
97
+ def notification_frame_key(header) = header.type
98
+
99
+ def notification_frame?(_endpoint, header, _payload)
100
+ header.type >= Raw::NLMSG_MIN_TYPE
101
+ end
102
+
103
+ def notification_class(_endpoint, header, _payload, classes)
104
+ classes[header.type]
105
+ end
106
+
107
+ def decode_notification(endpoint, header, payload, message_class)
108
+ decode_frame(endpoint, header, payload, message_class).message
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,146 @@
1
+ # Netlink wire definitions
2
+
3
+ require_relative '../endian'
4
+
5
+ module Nl
6
+ module Raw
7
+ # Constants from <linux/netlink.h>
8
+ module Constants
9
+ NETLINK_ROUTE = 0
10
+ NETLINK_NETFILTER = 12
11
+ NETLINK_GENERIC = 16
12
+
13
+ NLM_F_REQUEST = 1
14
+ NLM_F_MULTI = 2
15
+ NLM_F_ACK = 4
16
+ NLM_F_ECHO = 8
17
+ NLM_F_DUMP_INTR = 16
18
+ NLM_F_DUMP_FILTERED = 32
19
+ NLM_F_ROOT = 0x100
20
+ NLM_F_MATCH = 0x200
21
+ NLM_F_ATOMIC = 0x400
22
+ NLM_F_DUMP = NLM_F_ROOT | NLM_F_MATCH
23
+ NLM_F_REPLACE = 0x100
24
+ NLM_F_EXCL = 0x200
25
+ NLM_F_CREATE = 0x400
26
+ NLM_F_APPEND = 0x800
27
+
28
+ NLMSG_ALIGNTO = 4
29
+ NLMSG_HDRLEN = 16
30
+
31
+ NLMSG_NOOP = 0x1
32
+ NLMSG_ERROR = 0x2
33
+ NLMSG_DONE = 0x3
34
+ NLMSG_OVERRUN = 0x4
35
+
36
+ NLMSG_MIN_TYPE = 0x10
37
+
38
+ NLA_F_NESTED = 1 << 15
39
+ NLA_F_NET_BYTEORDER = 1 << 14
40
+ NLA_TYPE_MASK = ~(NLA_F_NESTED | NLA_F_NET_BYTEORDER)
41
+
42
+ NLA_ALIGNTO = 4
43
+ NLA_HDRLEN = 4
44
+ end
45
+ include Constants
46
+
47
+ # Fixed-format metadata header prepended to every Netlink message.
48
+ #
49
+ # This corresponds to Linux's +struct nlmsghdr+.
50
+ #
51
+ # @!attribute [rw] len
52
+ # @return [Integer] message length in bytes, including this header
53
+ # @!attribute [rw] type
54
+ # @return [Integer] message content type
55
+ # @!attribute [rw] flags
56
+ # @return [Integer] bitwise combination of +NLM_F_+ flags
57
+ # @!attribute [rw] seq
58
+ # @return [Integer] sequence number used to correlate requests and replies
59
+ # @!attribute [rw] pid
60
+ # @return [Integer] sender's Netlink port ID
61
+ NlMsgHdr = Struct.new(
62
+ :len, #: Integer
63
+ :type, #: Integer
64
+ :flags, #: Integer
65
+ :seq, #: Integer
66
+ :pid, #: Integer
67
+ )
68
+
69
+ class NlMsgHdr
70
+ FORMAT = Ractor.make_shareable([
71
+ Endian::Host::U32,
72
+ Endian::Host::U16,
73
+ Endian::Host::U16,
74
+ Endian::Host::U32,
75
+ Endian::Host::U32,
76
+ ])
77
+ private_constant :FORMAT
78
+
79
+ # Decodes a header from the decoder's current position.
80
+ #
81
+ # @param [Decoder] decoder the source decoder
82
+ # @return [NlMsgHdr] the decoded header
83
+ # @rbs (Decoder decoder) -> instance
84
+ def self.decode(decoder)
85
+ obj = new(*decoder.get_values(FORMAT))
86
+ decoder.align_to(Constants::NLMSG_ALIGNTO)
87
+ obj
88
+ end
89
+
90
+ # Encodes this header at the encoder's current position.
91
+ #
92
+ # @param [Encoder] encoder the destination encoder
93
+ # @return [void]
94
+ # @rbs (Encoder encoder) -> void
95
+ def encode(encoder)
96
+ encoder.reserve(Constants::NLMSG_HDRLEN)
97
+ encoder.put_values(FORMAT, to_a)
98
+ encoder.align_to(Constants::NLMSG_ALIGNTO)
99
+ end
100
+ end
101
+
102
+ # Fixed-format header prepended to every Netlink attribute.
103
+ #
104
+ # This corresponds to Linux's +struct nlattr+.
105
+ #
106
+ # @!attribute [rw] len
107
+ # @return [Integer] attribute length in bytes, including this header but
108
+ # excluding trailing alignment padding
109
+ # @!attribute [rw] type
110
+ # @return [Integer] attribute type combined with optional +NLA_F_+ flags
111
+ NlAttr = Struct.new(
112
+ :len, #: Integer
113
+ :type, #: Integer
114
+ )
115
+
116
+ class NlAttr
117
+ FORMAT = Ractor.make_shareable([
118
+ Endian::Host::U16,
119
+ Endian::Host::U16,
120
+ ])
121
+ private_constant :FORMAT
122
+
123
+ # Decodes an attribute header from the decoder's current position.
124
+ #
125
+ # @param [Decoder] decoder the source decoder
126
+ # @return [NlAttr] the decoded header
127
+ # @rbs (Decoder decoder) -> instance
128
+ def self.decode(decoder)
129
+ obj = new(*decoder.get_values(FORMAT))
130
+ decoder.align_to(Constants::NLA_ALIGNTO)
131
+ obj
132
+ end
133
+
134
+ # Encodes this attribute header at the encoder's current position.
135
+ #
136
+ # @param [Encoder] encoder the destination encoder
137
+ # @return [void]
138
+ # @rbs (Encoder encoder) -> void
139
+ def encode(encoder)
140
+ encoder.reserve(Constants::NLA_HDRLEN)
141
+ encoder.put_values(FORMAT, to_a)
142
+ encoder.align_to(Constants::NLA_ALIGNTO)
143
+ end
144
+ end
145
+ end
146
+ end
data/lib/nl/raw.rb ADDED
@@ -0,0 +1,104 @@
1
+ require_relative 'family'
2
+ require_relative 'raw/protocol'
3
+ require_relative 'raw/client'
4
+ require_relative 'attribute_set'
5
+ require_relative 'structured_payload'
6
+
7
+ module Nl
8
+ # Classic (Raw) Netlink families.
9
+ module Raw
10
+ # Base class for Raw Netlink families.
11
+ class Family < Nl::Family
12
+ # Opens a session for this raw Netlink family.
13
+ #
14
+ # @overload open(executor: nil, notification_capacity: DEFAULT_NOTIFICATION_CAPACITY)
15
+ # The caller is responsible for closing the session.
16
+ # @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
17
+ # @param [Integer] notification_capacity the maximum number of queued notifications
18
+ # @return [Family] the opened family session
19
+ # @overload open(executor: nil, notification_capacity: DEFAULT_NOTIFICATION_CAPACITY, &block)
20
+ # The session is automatically closed after the block returns.
21
+ # @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
22
+ # @param [Integer] notification_capacity the maximum number of queued notifications
23
+ # @yieldparam [Family] session the opened family session
24
+ # @return [Object] the value returned from the block
25
+ # @rbs (?executor: executor?, ?notification_capacity: Integer?) -> (Nl::Family::Session & instance)
26
+ # | [R] (?executor: executor?, ?notification_capacity: Integer?) { (instance) -> R } -> R
27
+ def self.open(executor: nil, notification_capacity: DEFAULT_NOTIFICATION_CAPACITY)
28
+ session = build_session(executor:, notification_capacity:)
29
+ return session unless block_given?
30
+
31
+ begin
32
+ yield session
33
+ ensure
34
+ session.close
35
+ end
36
+ end
37
+
38
+ class << self
39
+ # Builds a session that owns its underlying client.
40
+ #
41
+ # @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
42
+ # @param [Integer] notification_capacity the maximum number of queued notifications
43
+ # @return [Family] the opened family session
44
+ # @rbs (executor: executor?, notification_capacity: Integer) -> (Nl::Family::Session & instance)
45
+ private def build_session(executor:, notification_capacity:)
46
+ owner = Client.new(protonum: self::PROTONUM, executor:, notification_capacity:)
47
+ owner.family(self).extend(Nl::Family::Session)
48
+ rescue Exception
49
+ owner&.close
50
+ raise
51
+ end
52
+ end
53
+ end
54
+
55
+ # Base class for raw Netlink messages.
56
+ #
57
+ # A message may contain a fixed header followed by a set of Netlink
58
+ # attributes.
59
+ class Message
60
+ include Nl::StructuredPayload
61
+
62
+ # @param [Object, nil] fixed_header the family-specific fixed header
63
+ # @param [AttributeSet, nil] attributes the message's attributes
64
+ # @rbs (?untyped fixed_header, ?AttributeSet? attributes) -> void
65
+ def initialize(fixed_header = nil, attributes = self.class::ATTRIBUTE_SET.new)
66
+ super
67
+ end
68
+
69
+ # Appends an attribute to the message.
70
+ #
71
+ # @param [AttributeSet::Attribute] attribute the attribute to append
72
+ # @return [void]
73
+ # @rbs (AttributeSet::Attribute attribute) -> void
74
+ def append_attribute(attribute)
75
+ @attributes << attribute
76
+ end
77
+
78
+ # Decodes a raw Netlink message payload.
79
+ #
80
+ # @param [Decoder] decoder the source decoder
81
+ # @param [Integer] type the message type from its Netlink header
82
+ # @return [Message] the decoded message
83
+ # @raise [RuntimeError] if +type+ does not match the message class's type
84
+ # @rbs (Decoder decoder, type: Integer) -> instance
85
+ def self.decode(decoder, type:)
86
+ unless self::TYPE == type
87
+ raise "Expected message type #{self::TYPE}, got #{type}"
88
+ end
89
+
90
+ super(decoder)
91
+ end
92
+
93
+ class << self
94
+ # Returns the attribute names accepted by this message.
95
+ #
96
+ # @return [Array<Symbol>] the attribute names
97
+ # @rbs () -> Array[Symbol]
98
+ private def attribute_names
99
+ self::ATTRIBUTES
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -1,5 +1,3 @@
1
- # rbs_inline: enabled
2
-
3
1
  require_relative 'error'
4
2
 
5
3
  module Nl
@@ -0,0 +1,61 @@
1
+ module Nl
2
+ # Shared handling for a payload containing an optional fixed header followed
3
+ # by an optional attribute set.
4
+ module StructuredPayload
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ attr_accessor :fixed_header, :attributes
10
+
11
+ def initialize(fixed_header = nil, attributes = nil)
12
+ @fixed_header = fixed_header
13
+ @attributes = attributes
14
+ end
15
+
16
+ module ClassMethods
17
+ def from_params(params = nil, external_selectors: [], **keywords)
18
+ params = (params || {}).merge(keywords).transform_keys(&:to_sym)
19
+
20
+ if self::FIXED_HEADER
21
+ header_params = params.slice(*self::FIXED_HEADER.members)
22
+ fixed_header = self::FIXED_HEADER.new(**header_params)
23
+ else
24
+ header_params = {}
25
+ end
26
+
27
+ if self::ATTRIBUTE_SET
28
+ attribute_params = params.slice(*attribute_names)
29
+ attributes = self::ATTRIBUTE_SET.build_attributes(
30
+ attribute_params,
31
+ external_selectors:,
32
+ )
33
+ else
34
+ attribute_params = {}
35
+ end
36
+
37
+ unknown = params.keys - header_params.keys - attribute_params.keys
38
+ unless unknown.empty?
39
+ raise ArgumentError, "unknown parameters: #{unknown.join(', ')}"
40
+ end
41
+
42
+ new(fixed_header, attributes)
43
+ end
44
+
45
+ def decode(decoder, external_selectors: [])
46
+ fixed_header = self::FIXED_HEADER&.decode(decoder)
47
+ attributes = self::ATTRIBUTE_SET&.decode(decoder, external_selectors:)
48
+ new(fixed_header, attributes)
49
+ end
50
+
51
+ private def attribute_names
52
+ self::ATTRIBUTE_SET::BY_NAME.keys
53
+ end
54
+ end
55
+
56
+ def encode(encoder, external_selectors: [])
57
+ fixed_header&.encode(encoder)
58
+ attributes&.encode(encoder, external_selectors:)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,94 @@
1
+ require_relative 'structured_payload'
2
+
3
+ module Nl
4
+ module Selector
5
+ class MissingSelectorValueError < StandardError
6
+ attr_reader :scope, :index
7
+
8
+ def initialize(scope, index)
9
+ @scope = scope
10
+ @index = index
11
+ super()
12
+ end
13
+ end
14
+
15
+ class UnknownSelectorValueError < StandardError
16
+ attr_reader :value
17
+
18
+ def initialize(value)
19
+ @value = value
20
+ super()
21
+ end
22
+ end
23
+
24
+ module Reference
25
+ def select(context, formats)
26
+ value = read(context)
27
+ formats.fetch(value) do
28
+ return yield(value) if block_given?
29
+ raise UnknownSelectorValueError.new(value)
30
+ end
31
+ end
32
+ end
33
+
34
+ Local = Data.define(:index) do
35
+ include Reference
36
+
37
+ def read(context)
38
+ context.fetch_local(index)
39
+ end
40
+ end
41
+
42
+ External = Data.define(:index) do
43
+ include Reference
44
+
45
+ def read(context)
46
+ context.fetch_external(index)
47
+ end
48
+ end
49
+
50
+ class State
51
+ def initialize(local_count, external)
52
+ @local = Array.new(local_count)
53
+ @external = external
54
+ end
55
+
56
+ def set_local(index, value)
57
+ @local[index] = value
58
+ end
59
+
60
+ def fetch_local(index)
61
+ fetch(@local, index) { raise MissingSelectorValueError.new(:local, index) }
62
+ end
63
+
64
+ def fetch_external(index)
65
+ fetch(@external, index) { raise MissingSelectorValueError.new(:external, index) }
66
+ end
67
+
68
+ def fetch(values, key)
69
+ value = values[key]
70
+ value.nil? ? yield : value
71
+ end
72
+ end
73
+ end
74
+
75
+ # A selector-specific payload containing an optional fixed header followed by
76
+ # an optional set of Netlink attributes.
77
+ class SubMessage
78
+ include StructuredPayload
79
+ end
80
+
81
+ # An unrecognized selector-specific payload retained without interpretation.
82
+ class RawSubMessage < SubMessage
83
+ attr_reader :payload, :nlattr_type_flags
84
+
85
+ def initialize(payload, nlattr_type_flags:)
86
+ @payload = payload
87
+ @nlattr_type_flags = nlattr_type_flags
88
+ end
89
+
90
+ def encode(encoder, external_selectors: [])
91
+ encoder.put_string(payload)
92
+ end
93
+ end
94
+ end
data/lib/nl/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Nl
2
- VERSION = '0.3.0'
2
+ VERSION = '0.4.1'
3
3
  end
data/lib/nl.rb CHANGED
@@ -1,13 +1,12 @@
1
1
  require_relative 'nl/error'
2
- require_relative 'nl/core'
2
+ require_relative 'nl/bitfield32'
3
+ require_relative 'nl/raw/wire'
4
+ require_relative 'nl/structured_payload'
5
+ require_relative 'nl/sub_message'
3
6
  require_relative 'nl/notification'
4
7
  require_relative 'nl/connection'
5
8
  require_relative 'nl/family'
9
+ require_relative 'nl/raw'
6
10
  require_relative 'nl/genl'
7
11
  require_relative 'nl/socket'
8
12
  require_relative 'nl/version'
9
-
10
- module Nl
11
- include Core
12
- include Genl
13
- end
@@ -0,0 +1,53 @@
1
+ # Generated from lib/nl/async/dispatcher.rb with RBS::Inline
2
+
3
+ module Nl
4
+ module Async
5
+ # Owns the single receive loop for a socket and routes replies by sequence.
6
+ class Dispatcher
7
+ class Pending < Data
8
+ attr_reader exchange(): untyped
9
+
10
+ attr_reader endpoint(): untyped
11
+
12
+ attr_reader reply_class(): untyped
13
+
14
+ attr_reader sink(): untyped
15
+
16
+ def self.new: (untyped exchange, untyped endpoint, untyped reply_class, untyped sink) -> instance
17
+ | (exchange: untyped, endpoint: untyped, reply_class: untyped, sink: untyped) -> instance
18
+
19
+ def self.members: () -> [ :exchange, :endpoint, :reply_class, :sink ]
20
+
21
+ def members: () -> [ :exchange, :endpoint, :reply_class, :sink ]
22
+ end
23
+
24
+ def initialize: (untyped socket, protocol: untyped, notifications: untyped, ?executor: untyped) -> untyped
25
+
26
+ def exchange: (untyped endpoint, untyped kind, untyped request_class, untyped reply_class, untyped args) ?{ (?) -> untyped } -> untyped
27
+
28
+ def exchange_async: (untyped endpoint, untyped kind, untyped request_class, untyped reply_class, untyped args, ?stream_capacity: untyped) -> untyped
29
+
30
+ def async_capable?: () -> true
31
+
32
+ def receive_notification: (untyped endpoint, ?timeout: untyped) -> untyped
33
+
34
+ def close: () -> untyped
35
+
36
+ private def receive_loop: () -> untyped
37
+
38
+ private def fail_receive_loop: (untyped error) -> untyped
39
+
40
+ private def dispatch_datagram: (untyped buffer) -> untyped
41
+
42
+ private def dispatch_outcome: (untyped key, untyped pending, untyped outcome) -> untyped
43
+
44
+ private def complete_pending: (untyped key, untyped pending, untyped value) -> untyped
45
+
46
+ private def discard: (untyped key) -> untyped
47
+
48
+ private def fail_pending: (untyped key, untyped error) -> untyped
49
+
50
+ private def fail_all: (untyped error) -> untyped
51
+ end
52
+ end
53
+ end