nl 0.2.4 → 0.3.0
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 +8 -0
- data/lib/nl/async/dispatcher.rb +199 -0
- data/lib/nl/async/driver.rb +39 -0
- data/lib/nl/async/mailbox.rb +83 -0
- data/lib/nl/async/operation.rb +279 -0
- data/lib/nl/async.rb +15 -0
- data/lib/nl/blocking_transport.rb +131 -0
- data/lib/nl/connection.rb +63 -0
- data/lib/nl/core.rb +1 -1
- data/lib/nl/datagram.rb +24 -0
- data/lib/nl/decoder.rb +9 -1
- data/lib/nl/error.rb +10 -0
- data/lib/nl/exchange.rb +102 -0
- data/lib/nl/family.rb +134 -17
- data/lib/nl/genl/connection.rb +89 -0
- data/lib/nl/genl.rb +18 -31
- data/lib/nl/notification.rb +115 -0
- data/lib/nl/notification_router.rb +85 -0
- data/lib/nl/protocols/genl.rb +47 -28
- data/lib/nl/protocols/raw.rb +164 -99
- data/lib/nl/sequence_allocator.rb +36 -0
- data/lib/nl/socket.rb +15 -12
- data/lib/nl/version.rb +1 -1
- data/lib/nl.rb +5 -6
- metadata +20 -5
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
|
|
3
|
+
require_relative 'error'
|
|
4
|
+
|
|
5
|
+
module Nl
|
|
6
|
+
# A multicast group. `name` is the kernel-facing name.
|
|
7
|
+
# `id` is the fixed ID when the specification provides one.
|
|
8
|
+
McastGroup = Data.define(:name, :id)
|
|
9
|
+
|
|
10
|
+
# An unsolicited message whose wire type is not present in the loaded spec.
|
|
11
|
+
UnknownNotification = Data.define(:header, :payload)
|
|
12
|
+
|
|
13
|
+
# Thread-safe queue shared by a family's blocking and asynchronous facades.
|
|
14
|
+
class NotificationChannel
|
|
15
|
+
def initialize(capacity:)
|
|
16
|
+
raise ArgumentError, 'notification capacity must be positive' if capacity && capacity <= 0
|
|
17
|
+
|
|
18
|
+
@capacity = capacity
|
|
19
|
+
@mutex = Mutex.new
|
|
20
|
+
@condition = ConditionVariable.new
|
|
21
|
+
@queue = []
|
|
22
|
+
@error = nil
|
|
23
|
+
@closed = false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Adds a notification without ever blocking the socket receive loop.
|
|
27
|
+
# Returns false after the channel has been closed.
|
|
28
|
+
def push(notification)
|
|
29
|
+
@mutex.synchronize do
|
|
30
|
+
return false if @closed
|
|
31
|
+
|
|
32
|
+
if @capacity && @queue.length >= @capacity
|
|
33
|
+
lose!(NotificationLossError.new('notification queue capacity exceeded'))
|
|
34
|
+
else
|
|
35
|
+
wake = @queue.empty? && !@error
|
|
36
|
+
@queue << notification
|
|
37
|
+
@condition.signal if wake
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Records a broken notification boundary while allowing later delivery to
|
|
44
|
+
# resume after the consumer observes the error and resynchronizes state.
|
|
45
|
+
def fail(error)
|
|
46
|
+
@mutex.synchronize do
|
|
47
|
+
return false if @closed
|
|
48
|
+
|
|
49
|
+
lose!(error)
|
|
50
|
+
end
|
|
51
|
+
true
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def pop(timeout: nil)
|
|
55
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout if timeout
|
|
56
|
+
|
|
57
|
+
@mutex.synchronize do
|
|
58
|
+
loop do
|
|
59
|
+
if error = @error
|
|
60
|
+
@error = nil
|
|
61
|
+
raise error
|
|
62
|
+
end
|
|
63
|
+
return @queue.shift unless @queue.empty?
|
|
64
|
+
raise ClosedError, 'notification channel is closed' if @closed
|
|
65
|
+
|
|
66
|
+
remaining = deadline && deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
67
|
+
raise TimeoutError, 'notification receive timed out' if remaining && remaining <= 0
|
|
68
|
+
|
|
69
|
+
@condition.wait(@mutex, remaining)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def empty?
|
|
75
|
+
@mutex.synchronize { @queue.empty? && !@error }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def close
|
|
79
|
+
@mutex.synchronize do
|
|
80
|
+
return nil if @closed
|
|
81
|
+
|
|
82
|
+
@closed = true
|
|
83
|
+
@queue.clear
|
|
84
|
+
@error = nil
|
|
85
|
+
@condition.broadcast
|
|
86
|
+
end
|
|
87
|
+
nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private def lose!(error)
|
|
91
|
+
@queue.clear
|
|
92
|
+
@error = error
|
|
93
|
+
@condition.signal
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# An unbounded-in-time, single-family view of unsolicited messages.
|
|
98
|
+
class NotificationStream
|
|
99
|
+
include Enumerable
|
|
100
|
+
|
|
101
|
+
def initialize(&receive)
|
|
102
|
+
@receive = receive
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def next(timeout: nil)
|
|
106
|
+
@receive.call(timeout)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def each
|
|
110
|
+
return enum_for(__method__) unless block_given?
|
|
111
|
+
|
|
112
|
+
loop { yield self.next }
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
require_relative 'core'
|
|
2
|
+
require_relative 'notification'
|
|
3
|
+
|
|
4
|
+
module Nl
|
|
5
|
+
# Routes unsolicited frames to per-family notification channels.
|
|
6
|
+
class NotificationRouter
|
|
7
|
+
Entry = Struct.new(:protocol, :classes, :channel)
|
|
8
|
+
private_constant :Entry
|
|
9
|
+
|
|
10
|
+
def initialize(routing:, capacity:)
|
|
11
|
+
@routing = routing
|
|
12
|
+
@capacity = capacity
|
|
13
|
+
@mutex = Mutex.new
|
|
14
|
+
@entries = {}
|
|
15
|
+
@closed = false
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def register(protocol, classes)
|
|
19
|
+
@mutex.synchronize do
|
|
20
|
+
raise ClosedError, 'notification router is closed' if @closed
|
|
21
|
+
|
|
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
|
|
28
|
+
end
|
|
29
|
+
entry.channel
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def channel(protocol)
|
|
34
|
+
@mutex.synchronize do
|
|
35
|
+
@entries.fetch(@routing.family_key(protocol)).channel
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns true if the frame belongs to a registered notification family.
|
|
40
|
+
def route(header, payload)
|
|
41
|
+
if header.type == Core::NLMSG_OVERRUN
|
|
42
|
+
entries = @mutex.synchronize { @entries.values.dup }
|
|
43
|
+
entries.each { it.channel.fail(NotificationLossError.new('kernel reported Netlink overrun')) }
|
|
44
|
+
return true
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
entry = @mutex.synchronize do
|
|
48
|
+
@entries[@routing.frame_key(header)]
|
|
49
|
+
end
|
|
50
|
+
return false unless entry
|
|
51
|
+
return false unless entry.protocol.notification_frame?(header, payload)
|
|
52
|
+
|
|
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
|
|
59
|
+
entry.channel.push(notification)
|
|
60
|
+
true
|
|
61
|
+
rescue Exception => error
|
|
62
|
+
entry&.channel&.fail(error)
|
|
63
|
+
true
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def close
|
|
67
|
+
entries = @mutex.synchronize do
|
|
68
|
+
return nil if @closed
|
|
69
|
+
|
|
70
|
+
@closed = true
|
|
71
|
+
old = @entries.values
|
|
72
|
+
@entries.clear
|
|
73
|
+
old
|
|
74
|
+
end
|
|
75
|
+
entries.each { it.channel.close }
|
|
76
|
+
nil
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def lose_all(error)
|
|
80
|
+
entries = @mutex.synchronize { @entries.values.dup }
|
|
81
|
+
entries.each { it.channel.fail(error) }
|
|
82
|
+
nil
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
data/lib/nl/protocols/genl.rb
CHANGED
|
@@ -1,52 +1,71 @@
|
|
|
1
|
+
require_relative '../genl'
|
|
2
|
+
require_relative 'raw'
|
|
3
|
+
|
|
1
4
|
module Nl
|
|
2
5
|
module Protocols
|
|
3
6
|
# The Generic Netlink protocol
|
|
4
7
|
class Genl < Raw
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Endian::Host::U8,
|
|
9
|
-
Endian::Host::U8,
|
|
10
|
-
Endian::Host::U16,
|
|
11
|
-
])
|
|
12
|
-
|
|
13
|
-
def self.decode(decoder)
|
|
14
|
-
new(*decoder.get_values(FORMAT))
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def encode(encoder)
|
|
18
|
-
encoder.put_values(FORMAT, to_a)
|
|
19
|
-
end
|
|
8
|
+
class NotificationRouting
|
|
9
|
+
def family_key(protocol) = protocol.family_key
|
|
10
|
+
def frame_key(header) = header.type
|
|
20
11
|
end
|
|
21
12
|
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
NOTIFICATION_ROUTING = NotificationRouting.new.freeze
|
|
14
|
+
|
|
15
|
+
def self.protonum = Core::NETLINK_GENERIC
|
|
16
|
+
|
|
17
|
+
def initialize(name, family_id: nil, multicast_groups: {})
|
|
18
|
+
super(name, self.class.protonum)
|
|
24
19
|
@family_id = family_id || default_family_id(name)
|
|
20
|
+
@multicast_groups = multicast_groups.transform_keys(&:to_sym).freeze
|
|
25
21
|
end
|
|
26
22
|
|
|
27
23
|
def family_id
|
|
28
24
|
@family_id or raise NotImplementedError, "Genetlink family ID for '#{name}' must be resolved via nlctrl"
|
|
29
25
|
end
|
|
30
26
|
|
|
31
|
-
def encode_message(encoder,
|
|
32
|
-
|
|
27
|
+
def encode_message(encoder, request, seq:, pid:)
|
|
28
|
+
message = request.message
|
|
29
|
+
header = Core::NlMsgHdr.new(0, request.type, request.flags, seq, pid)
|
|
33
30
|
encoder.measure(Endian::Host::U16) do
|
|
34
|
-
|
|
35
|
-
message.
|
|
36
|
-
message.
|
|
37
|
-
GenlMsgHdr.new(cmd, 1, 0).encode(encoder)
|
|
38
|
-
message.fixed_header&.encode(encoder)
|
|
39
|
-
message.attributes.encode(encoder)
|
|
31
|
+
header.encode(encoder)
|
|
32
|
+
Nl::Genl::GenlMsgHdr.new(message.class::TYPE, 1, 0).encode(encoder)
|
|
33
|
+
message.encode(encoder)
|
|
40
34
|
end
|
|
41
35
|
end
|
|
42
36
|
|
|
43
37
|
class Message < Raw::Message
|
|
44
|
-
def self.decode(decoder,
|
|
45
|
-
genlhdr = GenlMsgHdr.decode(decoder)
|
|
46
|
-
super(decoder,
|
|
38
|
+
def self.decode(decoder, type:)
|
|
39
|
+
genlhdr = Nl::Genl::GenlMsgHdr.decode(decoder)
|
|
40
|
+
super(decoder, type: genlhdr.cmd)
|
|
47
41
|
end
|
|
48
42
|
end
|
|
49
43
|
|
|
44
|
+
def family_key = family_id
|
|
45
|
+
|
|
46
|
+
def self.notification_routing = NOTIFICATION_ROUTING
|
|
47
|
+
def notification_routing = self.class.notification_routing
|
|
48
|
+
|
|
49
|
+
def notification_frame?(header, _payload)
|
|
50
|
+
header.type == family_id
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def notification_class(header, payload, classes)
|
|
54
|
+
return unless notification_frame?(header, payload)
|
|
55
|
+
|
|
56
|
+
command = Nl::Genl::GenlMsgHdr.decode(Decoder.new(payload)).cmd
|
|
57
|
+
classes[command]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def multicast_group_id(name, _value)
|
|
61
|
+
@multicast_groups.fetch(name.to_sym) do
|
|
62
|
+
raise UnresolvedMulticastGroupError,
|
|
63
|
+
"Generic Netlink multicast group #{name.inspect} was not resolved"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private def frame_type(_message_class) = family_id
|
|
68
|
+
|
|
50
69
|
private def default_family_id(name)
|
|
51
70
|
Nl::Genl::GENL_ID_CTRL if name == 'nlctrl'
|
|
52
71
|
end
|
data/lib/nl/protocols/raw.rb
CHANGED
|
@@ -5,11 +5,20 @@ module Nl
|
|
|
5
5
|
module Protocols
|
|
6
6
|
# The raw Netlink protocol
|
|
7
7
|
class Raw
|
|
8
|
-
class
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
class NotificationRouting
|
|
9
|
+
def family_key(_protocol) = nil
|
|
10
|
+
def frame_key(_header) = nil
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
NOTIFICATION_ROUTING = NotificationRouting.new.freeze
|
|
14
|
+
|
|
15
|
+
AckFrame = Data.define(:header)
|
|
16
|
+
ErrorFrame = Data.define(:header, :errno)
|
|
17
|
+
DoneFrame = Data.define(:header, :errno)
|
|
18
|
+
UnknownFrame = Data.define(:header)
|
|
19
|
+
DataFrame = Data.define(:header, :message)
|
|
20
|
+
Request = Data.define(:type, :flags, :message)
|
|
21
|
+
|
|
13
22
|
attr_reader :name, :protonum
|
|
14
23
|
|
|
15
24
|
def initialize(name, protonum)
|
|
@@ -17,99 +26,91 @@ module Nl
|
|
|
17
26
|
@protonum = protonum
|
|
18
27
|
end
|
|
19
28
|
|
|
20
|
-
def encode_message(encoder,
|
|
21
|
-
|
|
29
|
+
def encode_message(encoder, request, seq:, pid:)
|
|
30
|
+
header = Core::NlMsgHdr.new(0, request.type, request.flags, seq, pid)
|
|
31
|
+
encoder.measure(Endian::Host::U16) do
|
|
32
|
+
header.encode(encoder)
|
|
33
|
+
request.message.encode(encoder)
|
|
34
|
+
end
|
|
22
35
|
end
|
|
23
36
|
|
|
24
37
|
def decode_message(decoder, message_class)
|
|
25
38
|
header = NlMsgHdr.decode(decoder)
|
|
26
39
|
decoder.limit(header.len - Core::NLMSG_HDRLEN) do
|
|
27
|
-
message_class.decode(decoder, header)
|
|
40
|
+
DataFrame.new(header:, message: message_class.decode(decoder, type: header.type))
|
|
28
41
|
end
|
|
29
42
|
end
|
|
30
43
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
44
|
+
# Decodes one frame using the reply class associated with its sequence.
|
|
45
|
+
def decode_frame(header, payload, message_class)
|
|
46
|
+
decoder = Decoder.new(payload)
|
|
47
|
+
if header.type < Core::NLMSG_MIN_TYPE
|
|
48
|
+
case header.type
|
|
49
|
+
when Core::NLMSG_ERROR
|
|
50
|
+
errno = decoder.get_value(Endian::Host::SINT)
|
|
51
|
+
if errno.positive?
|
|
52
|
+
raise ProtocolViolation, "expected zero or negative NLMSG_ERROR errno, got #{errno}"
|
|
53
|
+
end
|
|
38
54
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
raise binding.irb unless [header.seq, header.pid] == seq_pid
|
|
47
|
-
if header.type < Core::NLMSG_MIN_TYPE
|
|
48
|
-
# Control messages
|
|
49
|
-
case header.type
|
|
50
|
-
when Core::NLMSG_ERROR
|
|
51
|
-
errno = decoder.get_value(Endian::Host::SINT)
|
|
52
|
-
if errno == 0
|
|
53
|
-
yield Ack.new
|
|
54
|
-
else
|
|
55
|
-
yield SystemCallError.new(-errno)
|
|
56
|
-
end
|
|
57
|
-
decoder.skip(header.len - Core::NLMSG_HDRLEN - 4)
|
|
58
|
-
when Core::NLMSG_DONE
|
|
59
|
-
yield Done.new
|
|
60
|
-
decoder.skip(header.len - Core::NLMSG_HDRLEN)
|
|
61
|
-
else
|
|
62
|
-
# just ignore NLMSG_NOOP and other unknown control messages
|
|
63
|
-
decoder.skip(header.len - Core::NLMSG_HDRLEN)
|
|
55
|
+
errno.zero? ? AckFrame.new(header:) : ErrorFrame.new(header:, errno: -errno)
|
|
56
|
+
when Core::NLMSG_DONE
|
|
57
|
+
return DoneFrame.new(header:, errno: nil) unless decoder.available?
|
|
58
|
+
|
|
59
|
+
errno = decoder.get_value(Endian::Host::SINT)
|
|
60
|
+
if errno.positive?
|
|
61
|
+
raise ProtocolViolation, "expected zero or negative NLMSG_DONE errno, got #{errno}"
|
|
64
62
|
end
|
|
63
|
+
|
|
64
|
+
DoneFrame.new(header:, errno: errno.negative? ? -errno : nil)
|
|
65
65
|
else
|
|
66
|
-
|
|
67
|
-
decoder.limit(header.len - Core::NLMSG_HDRLEN) do
|
|
68
|
-
decoder.align_to(Core::NLMSG_ALIGNTO)
|
|
69
|
-
yield message_class.decode(decoder, header)
|
|
70
|
-
end
|
|
66
|
+
UnknownFrame.new(header:)
|
|
71
67
|
end
|
|
68
|
+
else
|
|
69
|
+
raise ArgumentError, 'reply class is required for a data message' unless message_class
|
|
70
|
+
|
|
71
|
+
DataFrame.new(header:, message: message_class.decode(decoder, type: header.type))
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
def send_message(socket, request, seq:, pid:)
|
|
76
|
+
encoder = Encoder.new
|
|
77
|
+
encode_message(encoder, request, seq:, pid:)
|
|
78
|
+
socket.sendmsg(encoder.buffer.get_string, 0, Socket.sockaddr_nl(0, 0))
|
|
79
|
+
nil
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def build_request(kind, request_class, args)
|
|
81
83
|
flags = Core::NLM_F_REQUEST
|
|
82
|
-
flags |=
|
|
83
|
-
|
|
84
|
-
request = request_class.from_params(args)
|
|
85
|
-
request.nlmsg_header.flags = flags
|
|
86
|
-
seq_pid = send_message(socket, request)
|
|
87
|
-
|
|
88
|
-
result = [] unless block_given?
|
|
89
|
-
|
|
90
|
-
done = false
|
|
91
|
-
begin
|
|
92
|
-
recv_message(socket, seq_pid, reply_class) do |message|
|
|
93
|
-
case message
|
|
94
|
-
when Done, Ack
|
|
95
|
-
done = true
|
|
96
|
-
when Exception
|
|
97
|
-
raise message
|
|
98
|
-
else
|
|
99
|
-
if block_given?
|
|
100
|
-
yield message
|
|
101
|
-
else
|
|
102
|
-
result << message
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
end until done
|
|
84
|
+
flags |= kind == :dump ? Core::NLM_F_DUMP : Core::NLM_F_ACK
|
|
107
85
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
86
|
+
message = request_class.from_params(args)
|
|
87
|
+
Request.new(type: frame_type(request_class), flags:, message:)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Raw Netlink sockets carry a single protocol family.
|
|
91
|
+
def notification_routing = NOTIFICATION_ROUTING
|
|
92
|
+
|
|
93
|
+
# Whether an unsolicited data frame belongs to this protocol.
|
|
94
|
+
def notification_frame?(header, _payload)
|
|
95
|
+
header.type >= Core::NLMSG_MIN_TYPE
|
|
111
96
|
end
|
|
112
97
|
|
|
98
|
+
# Looks up the generated notification class for an unsolicited frame.
|
|
99
|
+
def notification_class(header, _payload, classes)
|
|
100
|
+
classes[header.type]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def decode_notification(header, payload, message_class)
|
|
104
|
+
decode_frame(header, payload, message_class).message
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def multicast_group_id(name, value)
|
|
108
|
+
value or raise UnresolvedMulticastGroupError,
|
|
109
|
+
"multicast group #{name.inspect} has no fixed ID"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private def frame_type(message_class) = message_class::TYPE
|
|
113
|
+
|
|
113
114
|
class AttributeSet
|
|
114
115
|
Attribute = Struct.new(:value)
|
|
115
116
|
class Attribute
|
|
@@ -159,7 +160,9 @@ module Nl
|
|
|
159
160
|
end
|
|
160
161
|
|
|
161
162
|
private def encode1(encoder, attr)
|
|
162
|
-
|
|
163
|
+
datatype = attr.class::DATATYPE
|
|
164
|
+
type = attr.class::TYPE | datatype.nlattr_type_flags
|
|
165
|
+
nlattr = Core::NlAttr.new(0, type)
|
|
163
166
|
encoder.measure(Endian::Host::U16) do
|
|
164
167
|
nlattr.encode(encoder)
|
|
165
168
|
attr.encode(encoder)
|
|
@@ -208,10 +211,9 @@ module Nl
|
|
|
208
211
|
end
|
|
209
212
|
|
|
210
213
|
class Message
|
|
211
|
-
attr_accessor :
|
|
214
|
+
attr_accessor :fixed_header, :attributes
|
|
212
215
|
|
|
213
|
-
def initialize(
|
|
214
|
-
@nlmsg_header = header
|
|
216
|
+
def initialize(fixed_header = nil, attributes = self.class::ATTRIBUTE_SET.new)
|
|
215
217
|
@fixed_header = fixed_header
|
|
216
218
|
@attributes = attributes
|
|
217
219
|
end
|
|
@@ -230,8 +232,7 @@ module Nl
|
|
|
230
232
|
raise ArgumentError, "unknown parameters: #{unknown.join(', ')}"
|
|
231
233
|
end
|
|
232
234
|
|
|
233
|
-
|
|
234
|
-
new(header, fixed_header, attributes)
|
|
235
|
+
new(fixed_header, attributes)
|
|
235
236
|
end
|
|
236
237
|
|
|
237
238
|
def append_attribute(attribute)
|
|
@@ -239,14 +240,11 @@ module Nl
|
|
|
239
240
|
end
|
|
240
241
|
|
|
241
242
|
def encode(encoder)
|
|
242
|
-
encoder
|
|
243
|
-
|
|
244
|
-
@fixed_header&.encode(encoder)
|
|
245
|
-
@attributes.encode(encoder)
|
|
246
|
-
end
|
|
243
|
+
@fixed_header&.encode(encoder)
|
|
244
|
+
@attributes.encode(encoder)
|
|
247
245
|
end
|
|
248
246
|
|
|
249
|
-
def self.decode(decoder,
|
|
247
|
+
def self.decode(decoder, type:)
|
|
250
248
|
unless self::TYPE == type
|
|
251
249
|
raise "Expected message type #{self::TYPE}, got #{type}"
|
|
252
250
|
end
|
|
@@ -257,12 +255,18 @@ module Nl
|
|
|
257
255
|
|
|
258
256
|
attributes = self::ATTRIBUTE_SET.decode(decoder)
|
|
259
257
|
|
|
260
|
-
new(
|
|
258
|
+
new(fixed_header, attributes)
|
|
261
259
|
end
|
|
262
260
|
end
|
|
263
261
|
|
|
264
262
|
module DataTypes
|
|
265
|
-
class
|
|
263
|
+
class Base
|
|
264
|
+
def nlattr_type_flags
|
|
265
|
+
0
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
class Scalar < Base
|
|
266
270
|
def initialize(type, check)
|
|
267
271
|
@type = type
|
|
268
272
|
@check = check
|
|
@@ -278,7 +282,7 @@ module Nl
|
|
|
278
282
|
end
|
|
279
283
|
end
|
|
280
284
|
|
|
281
|
-
class String
|
|
285
|
+
class String < Base
|
|
282
286
|
def initialize(check)
|
|
283
287
|
@check = check
|
|
284
288
|
end
|
|
@@ -292,7 +296,7 @@ module Nl
|
|
|
292
296
|
end
|
|
293
297
|
end
|
|
294
298
|
|
|
295
|
-
class Binary
|
|
299
|
+
class Binary < Base
|
|
296
300
|
def initialize(check)
|
|
297
301
|
@check = check
|
|
298
302
|
end
|
|
@@ -306,7 +310,7 @@ module Nl
|
|
|
306
310
|
end
|
|
307
311
|
end
|
|
308
312
|
|
|
309
|
-
class Flag
|
|
313
|
+
class Flag < Base
|
|
310
314
|
def encode(encoder, value)
|
|
311
315
|
# flag attribute has no payload; presence encodes true
|
|
312
316
|
end
|
|
@@ -317,7 +321,7 @@ module Nl
|
|
|
317
321
|
end
|
|
318
322
|
|
|
319
323
|
# A 32-bit value paired with a selector mask (8 bytes total: value u32 + selector u32)
|
|
320
|
-
class Bitfield32
|
|
324
|
+
class Bitfield32 < Base
|
|
321
325
|
def encode(encoder, value)
|
|
322
326
|
v, selector = value.is_a?(Array) ? value : [value, 0xFFFFFFFF]
|
|
323
327
|
encoder.put_value(Endian::Host::U32, v)
|
|
@@ -331,7 +335,7 @@ module Nl
|
|
|
331
335
|
end
|
|
332
336
|
end
|
|
333
337
|
|
|
334
|
-
class Pad
|
|
338
|
+
class Pad < Base
|
|
335
339
|
def initialize(length = nil)
|
|
336
340
|
@length = length
|
|
337
341
|
end
|
|
@@ -346,13 +350,21 @@ module Nl
|
|
|
346
350
|
end
|
|
347
351
|
end
|
|
348
352
|
|
|
349
|
-
class NestedAttributes
|
|
353
|
+
class NestedAttributes < Base
|
|
350
354
|
def initialize(attribute_set)
|
|
351
355
|
@attribute_set = attribute_set
|
|
352
356
|
end
|
|
353
357
|
|
|
358
|
+
def nlattr_type_flags
|
|
359
|
+
Core::NLA_F_NESTED
|
|
360
|
+
end
|
|
361
|
+
|
|
354
362
|
def encode(encoder, value)
|
|
355
|
-
@attribute_set
|
|
363
|
+
unless value.is_a?(@attribute_set)
|
|
364
|
+
raise TypeError, "value must be an instance of #{@attribute_set}"
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
value.encode(encoder)
|
|
356
368
|
end
|
|
357
369
|
|
|
358
370
|
def decode(decoder)
|
|
@@ -360,7 +372,60 @@ module Nl
|
|
|
360
372
|
end
|
|
361
373
|
end
|
|
362
374
|
|
|
363
|
-
|
|
375
|
+
# Nested attributes whose type numbers are keys rather than members of
|
|
376
|
+
# an attribute set. Each level is returned as an Integer-keyed Hash;
|
|
377
|
+
# values at the innermost level are decoded as @attribute_set.
|
|
378
|
+
class NestTypeValue < Base
|
|
379
|
+
def initialize(attribute_set, levels)
|
|
380
|
+
raise ArgumentError, 'levels must be positive' unless levels.positive?
|
|
381
|
+
|
|
382
|
+
@attribute_set = attribute_set
|
|
383
|
+
@levels = levels
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def encode(encoder, value)
|
|
387
|
+
encode_level(encoder, value, @levels)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def decode(decoder)
|
|
391
|
+
decode_level(decoder, @levels)
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
private def encode_level(encoder, values, levels)
|
|
395
|
+
values.each do |type, value|
|
|
396
|
+
nlattr = Core::NlAttr.new(0, type | Core::NLA_F_NESTED)
|
|
397
|
+
encoder.measure(Endian::Host::U16) do
|
|
398
|
+
nlattr.encode(encoder)
|
|
399
|
+
if levels == 1
|
|
400
|
+
value.encode(encoder)
|
|
401
|
+
else
|
|
402
|
+
encode_level(encoder, value, levels - 1)
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
encoder.align_to(Core::NLA_ALIGNTO)
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
private def decode_level(decoder, levels)
|
|
410
|
+
result = {}
|
|
411
|
+
while decoder.available?(Core::NLA_HDRLEN)
|
|
412
|
+
nlattr = Core::NlAttr.decode(decoder)
|
|
413
|
+
type = nlattr.type & Core::NLA_TYPE_MASK
|
|
414
|
+
value = decoder.limit(nlattr.len - Core::NLA_HDRLEN) do
|
|
415
|
+
if levels == 1
|
|
416
|
+
@attribute_set.decode(decoder)
|
|
417
|
+
else
|
|
418
|
+
decode_level(decoder, levels - 1)
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
decoder.align_to(Core::NLA_ALIGNTO)
|
|
422
|
+
result[type] = value
|
|
423
|
+
end
|
|
424
|
+
result
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
class IndexedArray < Base
|
|
364
429
|
def initialize(sub_type)
|
|
365
430
|
@sub_type = sub_type
|
|
366
431
|
end
|