ynl 0.1.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 +7 -0
- data/.rspec +3 -0
- data/Rakefile +6 -0
- data/lib/ynl/family.rb +20 -0
- data/lib/ynl/generator.rb +230 -0
- data/lib/ynl/models.rb +156 -0
- data/lib/ynl/parser.rb +330 -0
- data/lib/ynl.rb +47 -0
- data/test.rb +10 -0
- metadata +79 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b6a0856ea9a2918cc0a1dee7d9452227091209776df336059a4eae4ca85b903e
|
|
4
|
+
data.tar.gz: 4513210c607ad814734cc0ed03f5e57ed1853da3b86539015a933db6364c5205
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ba1fd6ff654ad9c613e98ddcbdf9e28e3b9b24eac873f5e1c19aa2cfa919e2020bc9c7557691d235554f72cc279f89e4612027aef1feaeda43c3d9a98201f76d
|
|
7
|
+
data.tar.gz: 39ca59b427aa8d8c945350a351518063407583e7e511acf0e4ed5ba901b860dbab65b32daaf0a1218ccdbf7fddd6d0a5c826761f80393f88b361159a8fee4be9
|
data/.rspec
ADDED
data/Rakefile
ADDED
data/lib/ynl/family.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'stringio'
|
|
2
|
+
|
|
3
|
+
require_relative 'parser'
|
|
4
|
+
require_relative 'generator'
|
|
5
|
+
|
|
6
|
+
module Ynl
|
|
7
|
+
class Family
|
|
8
|
+
def self.build(path)
|
|
9
|
+
require 'nl'
|
|
10
|
+
|
|
11
|
+
defs = Ynl::Parser.parse_file(path)
|
|
12
|
+
|
|
13
|
+
buf = StringIO.new
|
|
14
|
+
classname = Generator.new(defs, buf).generate(namespace: 'self')
|
|
15
|
+
classdef = buf.string
|
|
16
|
+
|
|
17
|
+
Module.new { eval(classdef) }.const_get(classname)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
module Ynl
|
|
2
|
+
class Generator
|
|
3
|
+
module Refinements
|
|
4
|
+
WORD_DELIM = /[ _-]+/
|
|
5
|
+
|
|
6
|
+
refine(String) do
|
|
7
|
+
def as_const_name
|
|
8
|
+
split(WORD_DELIM).map(&:upcase).join('_')
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def as_class_name
|
|
12
|
+
split(WORD_DELIM).map(&:capitalize).join
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def as_variable_name
|
|
16
|
+
split(WORD_DELIM).join('_')
|
|
17
|
+
end
|
|
18
|
+
alias as_method_name as_variable_name
|
|
19
|
+
|
|
20
|
+
def as_string_literal
|
|
21
|
+
dump
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def as_symbol_literal
|
|
25
|
+
":#{dump}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
private_constant :Refinements
|
|
30
|
+
|
|
31
|
+
using Refinements
|
|
32
|
+
|
|
33
|
+
def initialize(ynl, out)
|
|
34
|
+
@ynl = ynl
|
|
35
|
+
@out = out
|
|
36
|
+
@indent = 0
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def generate(superclass: '::Nl::Family', namespace: nil)
|
|
40
|
+
classname = @ynl.name.as_class_name
|
|
41
|
+
emit_class([*namespace, classname].join('::'), superclass) do
|
|
42
|
+
write('NAME = ', @ynl.name.as_string_literal)
|
|
43
|
+
write('PROTONUM = ', @ynl.protonum)
|
|
44
|
+
|
|
45
|
+
if @ynl.protocol == 'netlink-raw'
|
|
46
|
+
write('PROTOCOL = Ractor.make_shareable(::Nl::Protocols::Raw.new(', @ynl.name.as_string_literal, ', ', @ynl.protonum ,'))')
|
|
47
|
+
else
|
|
48
|
+
write('PROTOCOL = Ractor.make_shareable(::Nl::Protocols::Genl.new(', @ynl.name.as_string_literal, ')')
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
emit_module('Structs') do
|
|
52
|
+
@ynl.structs.each do |name, struct|
|
|
53
|
+
emit_comment(struct.doc)
|
|
54
|
+
write(name.as_class_name, ' = Struct.new(', struct.members.map { it.name.as_variable_name.as_symbol_literal }.join(', ') ,')')
|
|
55
|
+
emit_class(name.as_class_name) do
|
|
56
|
+
write('MEMBERS = Ractor.make_shareable({', struct.members.map { "#{it.name.as_variable_name}: #{to_datatype(it.type, nil)}" }.join(', ') ,'})')
|
|
57
|
+
write('def self.decode(decoder)')
|
|
58
|
+
indent do
|
|
59
|
+
write('self.new(*MEMBERS.map {|name, datatype| datatype.decode(decoder) })')
|
|
60
|
+
end
|
|
61
|
+
write('end')
|
|
62
|
+
|
|
63
|
+
write('def encode(encoder)')
|
|
64
|
+
indent do
|
|
65
|
+
write('MEMBERS.each {|name, datatype| datatype.encode(encoder, self.public_send(name)) }')
|
|
66
|
+
end
|
|
67
|
+
write('end')
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
emit_module('AttributeSets') do
|
|
73
|
+
@ynl.attribute_sets.each do |name, attr_set|
|
|
74
|
+
emit_comment(attr_set.doc)
|
|
75
|
+
emit_class(name.as_class_name, '::Nl::Family::AttributeSet') do
|
|
76
|
+
emit_comment("Abstract class")
|
|
77
|
+
emit_class('Attribute', '::Nl::Family::AttributeSet::Attribute') do
|
|
78
|
+
end
|
|
79
|
+
attr_set.attributes.each do |attr|
|
|
80
|
+
emit_comment(attr.doc)
|
|
81
|
+
emit_class(attr.name.as_class_name, 'Attribute') do
|
|
82
|
+
write('TYPE = ', attr.value)
|
|
83
|
+
write('NAME =', attr.name.as_variable_name.as_symbol_literal)
|
|
84
|
+
write('DATATYPE = ', to_datatype(attr.type, attr.checks))
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
write('BY_NAME = Ractor.make_shareable({', attr_set.attributes.map { "#{it.name.as_variable_name.as_symbol_literal} => #{it.name.as_class_name}" }.join(', ') ,'})')
|
|
89
|
+
write('BY_TYPE = Ractor.make_shareable({', attr_set.attributes.map { "#{it.value} => #{it.name.as_class_name}" }.join(', ') ,'})')
|
|
90
|
+
|
|
91
|
+
emit_singleton_class do
|
|
92
|
+
emit_rbs_comment(
|
|
93
|
+
'name: Symbol',
|
|
94
|
+
'return: Attribute',
|
|
95
|
+
)
|
|
96
|
+
write('def by_name(name) = BY_NAME[name]')
|
|
97
|
+
|
|
98
|
+
emit_rbs_comment(
|
|
99
|
+
'type: Integer',
|
|
100
|
+
'return: Attribute',
|
|
101
|
+
)
|
|
102
|
+
write('def by_type(type) = BY_TYPE[type]')
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
emit_module('Messages') do
|
|
109
|
+
@ynl.operations.each do |name, oper|
|
|
110
|
+
emit_comment(oper.doc)
|
|
111
|
+
%w[do dump].each do |method|
|
|
112
|
+
if request_reply = oper.public_send(method + 'it')
|
|
113
|
+
%w[request reply].to_h { [it, request_reply.public_send(it)] }.compact.each do |type, msg|
|
|
114
|
+
emit_class(method.as_class_name + oper.name.as_class_name + type.as_class_name, '::Nl::Family::Message') do
|
|
115
|
+
write('TYPE = ', msg.value)
|
|
116
|
+
write('FIXED_HEADER = ', oper.fixed_header&.then { 'Structs::' + it.name.as_class_name } || 'nil')
|
|
117
|
+
write('ATTRIBUTE_SET = AttributeSets::', oper.attribute_set.name.as_class_name)
|
|
118
|
+
params = msg.attributes
|
|
119
|
+
header_params = params & (oper.fixed_header&.members&.map(&:name) || [])
|
|
120
|
+
attribute_params = params & (oper.attribute_set.attributes.map(&:name))
|
|
121
|
+
write('HEADER_PARAMS = Ractor.make_shareable(%i[', header_params.map { it.as_variable_name }.join(' ') ,'])')
|
|
122
|
+
write('ATTRIBUTE_PARAMS = Ractor.make_shareable(%i[', attribute_params.map { it.as_variable_name }.join(' ') ,'])')
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# emit request methods
|
|
131
|
+
@ynl.operations.each do |name, oper|
|
|
132
|
+
%w[do dump].each do |method|
|
|
133
|
+
if request_reply = oper.public_send(method + 'it')
|
|
134
|
+
emit_comment(oper.doc)
|
|
135
|
+
write('def ', method.as_method_name, '_', oper.name.as_method_name, '(**args)')
|
|
136
|
+
indent do
|
|
137
|
+
request_class = "Messages::#{method.as_class_name}#{oper.name.as_class_name}Request"
|
|
138
|
+
if request_reply.reply
|
|
139
|
+
reply_class = "Messages::#{method.as_class_name}#{oper.name.as_class_name}Reply"
|
|
140
|
+
else
|
|
141
|
+
reply_class = 'nil'
|
|
142
|
+
end
|
|
143
|
+
write("exchange_message(#{method.as_symbol_literal}, #{request_class}, #{reply_class}, args)")
|
|
144
|
+
end
|
|
145
|
+
write('end')
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
classname
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
INDENT = -' '
|
|
155
|
+
NEWLINE = -?\n
|
|
156
|
+
private_constant :INDENT, :NEWLINE
|
|
157
|
+
|
|
158
|
+
private def write(*str)
|
|
159
|
+
@out.write(INDENT * @indent, *str, NEWLINE)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
private def indent
|
|
163
|
+
@indent += 1
|
|
164
|
+
yield
|
|
165
|
+
ensure
|
|
166
|
+
@indent -= 1
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
private def emit_module(name)
|
|
170
|
+
write('module ', name)
|
|
171
|
+
indent { yield }
|
|
172
|
+
write('end')
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
private def emit_class(name, superclass = nil)
|
|
176
|
+
if superclass
|
|
177
|
+
write('class ', name, ' < ', superclass)
|
|
178
|
+
else
|
|
179
|
+
write('class ', name)
|
|
180
|
+
end
|
|
181
|
+
indent { yield }
|
|
182
|
+
write('end')
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
private def emit_singleton_class
|
|
186
|
+
write('class << self')
|
|
187
|
+
indent { yield }
|
|
188
|
+
write('end')
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
private def emit_comment(comment)
|
|
192
|
+
return unless comment
|
|
193
|
+
comment.each_line do |line|
|
|
194
|
+
write('# ', line)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
private def emit_rbs_comment(*args)
|
|
199
|
+
args.each do |arg|
|
|
200
|
+
write('# @rbs ', arg)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
private def to_datatype(type, checks)
|
|
205
|
+
case type
|
|
206
|
+
when Types::Pad
|
|
207
|
+
'nil'
|
|
208
|
+
when Types::Scalar
|
|
209
|
+
"PROTOCOL.class::DataTypes::Scalar.new(::Nl::Endian::#{type.byte_order.name.as_class_name}::#{type.type.as_class_name}, check: #{to_checks(checks)})"
|
|
210
|
+
when Types::String
|
|
211
|
+
"PROTOCOL.class::DataTypes::String.new(check: #{to_checks(checks)})"
|
|
212
|
+
when Types::Binary
|
|
213
|
+
# if type.struct
|
|
214
|
+
# "Structs::" + type.struct.name.as_class_name
|
|
215
|
+
# else
|
|
216
|
+
"PROTOCOL.class::DataTypes::Binary.new(check: #{to_checks(checks)})"
|
|
217
|
+
# end
|
|
218
|
+
when Types::NestedAttributes
|
|
219
|
+
"PROTOCOL.class::DataTypes::NestedAttributes.new(#{type.attribute_set.name.as_class_name})"
|
|
220
|
+
else
|
|
221
|
+
raise "Unknown type: #{type.class}"
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
private def to_checks(checks)
|
|
226
|
+
return 'nil' if !checks || checks.empty?
|
|
227
|
+
%Q{-> { #{checks.join('; ')} }}
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
data/lib/ynl/models.rb
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
module Ynl
|
|
2
|
+
module Models
|
|
3
|
+
Family = ::Struct.new(:name, :protocol, :protonum, :doc,
|
|
4
|
+
:consts, :enums, :flags, :structs,
|
|
5
|
+
:attribute_sets, :sub_messages, :operations, :mcast_groups)
|
|
6
|
+
class Family
|
|
7
|
+
def resolve
|
|
8
|
+
structs.transform_values! {|s| s.resolve(self) }
|
|
9
|
+
attribute_sets.transform_values! {|s| s.resolve(self) }
|
|
10
|
+
self
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def find_type(name)
|
|
14
|
+
enums[name] or flags[name] or structs[name] or raise ParseError, "Unknown type: #{name}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class Thunk
|
|
19
|
+
def initialize(&block)
|
|
20
|
+
@block = block
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def resolve(f)
|
|
24
|
+
raise ParseError, "Circular dependency" if @resolving
|
|
25
|
+
@resolving = true
|
|
26
|
+
@block.call(f)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class Enum
|
|
31
|
+
Entry = ::Struct.new(:name, :value, :doc)
|
|
32
|
+
|
|
33
|
+
attr_reader :name, :entries, :doc
|
|
34
|
+
|
|
35
|
+
def initialize(name:, doc:)
|
|
36
|
+
@name = name
|
|
37
|
+
@entries = []
|
|
38
|
+
@doc = doc
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def resolve(f)
|
|
42
|
+
self
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class Flags
|
|
47
|
+
Entry = ::Struct.new(:name, :value, :doc)
|
|
48
|
+
|
|
49
|
+
attr_reader :name, :entries, :doc
|
|
50
|
+
|
|
51
|
+
def initialize(name:, doc:)
|
|
52
|
+
@name = name
|
|
53
|
+
@entries = []
|
|
54
|
+
@doc = doc
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def resolve(f)
|
|
58
|
+
self
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class Struct
|
|
63
|
+
Member = ::Struct.new(:name, :type, :doc) do
|
|
64
|
+
def resolve(f)
|
|
65
|
+
self.type = self.type.resolve(f)
|
|
66
|
+
self
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
attr_reader :name, :members, :doc
|
|
71
|
+
|
|
72
|
+
def initialize(name:, doc:)
|
|
73
|
+
@name = name
|
|
74
|
+
@members = []
|
|
75
|
+
@doc = doc
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def resolve(f)
|
|
79
|
+
@members.map! {|m| m.resolve(f) }
|
|
80
|
+
self
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
class AttributeSet
|
|
85
|
+
Attribute = ::Struct.new(:name, :type, :value, :checks, :doc) do
|
|
86
|
+
def resolve(f)
|
|
87
|
+
self.type = type.resolve(f)
|
|
88
|
+
self
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
attr_reader :name, :name_prefix, :attributes, :doc
|
|
93
|
+
|
|
94
|
+
def initialize(name:, name_prefix:, doc:)
|
|
95
|
+
@name = name
|
|
96
|
+
@name_prefix = name_prefix
|
|
97
|
+
@attributes = []
|
|
98
|
+
@doc = doc
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def resolve(f)
|
|
102
|
+
attributes.map! { it.resolve(f) }
|
|
103
|
+
self
|
|
104
|
+
rescue
|
|
105
|
+
raise ParseError, "Failed to resolve attribute set: #{name}"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
class Operation
|
|
110
|
+
attr_reader :name, :doc, :fixed_header, :attribute_set, :doit, :dumpit
|
|
111
|
+
|
|
112
|
+
def initialize(name:, doc:, fixed_header:, attribute_set:, doit:, dumpit:)
|
|
113
|
+
@name = name
|
|
114
|
+
@doc = doc
|
|
115
|
+
@fixed_header = fixed_header
|
|
116
|
+
@attribute_set = attribute_set
|
|
117
|
+
@doit = doit
|
|
118
|
+
@dumpit = dumpit
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def resolve(f)
|
|
122
|
+
@doit = @doit.resolve(f)
|
|
123
|
+
@dumpit = @dumpit.resolve(f)
|
|
124
|
+
self
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
class RequestReply
|
|
129
|
+
attr_reader :request, :reply
|
|
130
|
+
|
|
131
|
+
def initialize(request:, reply:)
|
|
132
|
+
@request = request
|
|
133
|
+
@reply = reply
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def resolve(f)
|
|
137
|
+
@request = @request.resolve(f)
|
|
138
|
+
@reply = @reply.resolve(f)
|
|
139
|
+
self
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
class Message
|
|
144
|
+
attr_reader :value, :attributes
|
|
145
|
+
|
|
146
|
+
def initialize(value:, attributes:)
|
|
147
|
+
@value = value
|
|
148
|
+
@attributes = attributes
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def resolve(f)
|
|
152
|
+
self
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
data/lib/ynl/parser.rb
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
|
|
3
|
+
require_relative 'models'
|
|
4
|
+
|
|
5
|
+
module Ynl
|
|
6
|
+
class Parser
|
|
7
|
+
def initialize(source)
|
|
8
|
+
@yaml = YAML.load(source, aliases: true)
|
|
9
|
+
|
|
10
|
+
@consts = {}
|
|
11
|
+
@flags = {}
|
|
12
|
+
@enums = {}
|
|
13
|
+
@structs = {}
|
|
14
|
+
|
|
15
|
+
@attribute_sets = {}
|
|
16
|
+
@sub_messages = {}
|
|
17
|
+
@operations = {}
|
|
18
|
+
@mcast_groups = {}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.parse_file(path)
|
|
22
|
+
File.open(path) {|f| new(f) }.parse
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def parse
|
|
26
|
+
protocol = @yaml['protocol'] || 'genetlink'
|
|
27
|
+
protonum = @yaml['protonum']
|
|
28
|
+
name = @yaml['name']
|
|
29
|
+
doc = @yaml['doc']
|
|
30
|
+
|
|
31
|
+
@yaml['definitions']&.each do |d|
|
|
32
|
+
parse_definition(d)
|
|
33
|
+
end
|
|
34
|
+
@yaml['attribute-sets'].each do |d|
|
|
35
|
+
parse_attribute_set(d)
|
|
36
|
+
end
|
|
37
|
+
@yaml['sub-messages']&.each do |d|
|
|
38
|
+
parse_sub_message(d)
|
|
39
|
+
end
|
|
40
|
+
if operations = @yaml['operations']
|
|
41
|
+
enum_model = case operations['enum-model']
|
|
42
|
+
when 'directional'
|
|
43
|
+
:directional
|
|
44
|
+
when nil
|
|
45
|
+
:unidirectional
|
|
46
|
+
else
|
|
47
|
+
raise ParseError, "Unknown enum model: #{operations['enum-model']}"
|
|
48
|
+
end
|
|
49
|
+
@default_fixed_header = operations['fixed-header']&.then { @structs.fetch(it) }
|
|
50
|
+
operations['list']&.each do |d|
|
|
51
|
+
parse_operation(d, enum_model:)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
@yaml['mcast-groups']&.each do |d|
|
|
55
|
+
parse_mcast_group(d)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
Models::Family.new(
|
|
59
|
+
name:,
|
|
60
|
+
protocol:,
|
|
61
|
+
protonum:,
|
|
62
|
+
doc:,
|
|
63
|
+
consts: @consts,
|
|
64
|
+
enums: @enums,
|
|
65
|
+
flags: @flags,
|
|
66
|
+
structs: @structs,
|
|
67
|
+
attribute_sets: @attribute_sets,
|
|
68
|
+
sub_messages: @sub_messages,
|
|
69
|
+
operations: @operations,
|
|
70
|
+
mcast_groups: @mcast_groups,
|
|
71
|
+
).resolve
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private def parse_definition(d)
|
|
75
|
+
case type = d.fetch('type')
|
|
76
|
+
when 'const'
|
|
77
|
+
v = parse_const(d)
|
|
78
|
+
@consts[v.name] = v
|
|
79
|
+
when 'enum'
|
|
80
|
+
v = parse_enum_flags(d, type: :enum)
|
|
81
|
+
@enums[v.name] = v
|
|
82
|
+
when 'flags'
|
|
83
|
+
v = parse_enum_flags(d, type: :flags)
|
|
84
|
+
@flags[v.name] = v
|
|
85
|
+
when 'struct'
|
|
86
|
+
v = parse_struct(d)
|
|
87
|
+
@structs[v.name] = v
|
|
88
|
+
else
|
|
89
|
+
raise ParseError, "Unknown definition type: #{type}"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private def parse_enum_flags(d, type:)
|
|
94
|
+
cls = type == :enum ? Models::Enum : Models::Flags
|
|
95
|
+
result = cls.new(name: d.fetch('name'), doc: d['doc'])
|
|
96
|
+
|
|
97
|
+
start_value = d['start-value'] || 0
|
|
98
|
+
value = type == :enum ? start_value : 1 << start_value
|
|
99
|
+
|
|
100
|
+
d.fetch('entries').each do |v|
|
|
101
|
+
case v
|
|
102
|
+
when String
|
|
103
|
+
entry = cls::Entry.new(name: v, value:)
|
|
104
|
+
when Hash
|
|
105
|
+
entry = cls::Entry.new(name: v.fetch('name'), value:, doc: v['doc'])
|
|
106
|
+
else
|
|
107
|
+
raise ParseError, "Unknown class for enum/flags entry: #{v.class}"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
result.entries << entry
|
|
111
|
+
|
|
112
|
+
value = type == :enum ? value + 1 : value << 1
|
|
113
|
+
|
|
114
|
+
rescue
|
|
115
|
+
raise ParseError, "Failed to parse enum/flags entry: #{v.fetch('name')}"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
result
|
|
119
|
+
rescue
|
|
120
|
+
raise ParseError, "Failed to parse enum/flags: #{d.fetch('name')}"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
private def parse_struct_member_type(d)
|
|
124
|
+
type = d.fetch('type')
|
|
125
|
+
case type
|
|
126
|
+
when 'u8', 'u16', 'u32', 'u64', 's8', 's16', 's32', 's64', 'int', 'uint'
|
|
127
|
+
Types::Scalar.new(
|
|
128
|
+
type: type,
|
|
129
|
+
byte_order: parse_byte_order(d['byte-order']),
|
|
130
|
+
)
|
|
131
|
+
when 'binary'
|
|
132
|
+
Types::Binary.new(
|
|
133
|
+
struct: d['struct'] ? Models::Thunk.new {|f| f.structs.fetch(d['struct']) } : nil,
|
|
134
|
+
display_hint: d['display-hint'],
|
|
135
|
+
)
|
|
136
|
+
when 'pad'
|
|
137
|
+
Types::Pad.new(
|
|
138
|
+
length: d['len'],
|
|
139
|
+
)
|
|
140
|
+
else
|
|
141
|
+
fail "Unknown type: #{type}"
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private def parse_attribute_type(d)
|
|
146
|
+
type = d.fetch('type')
|
|
147
|
+
case type
|
|
148
|
+
when 'u8', 'u16', 'u32', 'u64', 's8', 's16', 's32', 's64', 'int', 'uint'
|
|
149
|
+
Types::Scalar.new(
|
|
150
|
+
type: type,
|
|
151
|
+
byte_order: parse_byte_order(d['byte-order']),
|
|
152
|
+
)
|
|
153
|
+
when 'binary'
|
|
154
|
+
Types::Binary.new(
|
|
155
|
+
struct: d['struct'] ? Models::Thunk.new {|f| f.structs.fetch(d.fetch('struct')) } : nil,
|
|
156
|
+
display_hint: d['display-hint'],
|
|
157
|
+
)
|
|
158
|
+
when 'string'
|
|
159
|
+
Types::String.new
|
|
160
|
+
when 'nest'
|
|
161
|
+
Types::NestedAttributes.new(
|
|
162
|
+
attribute_set: Models::Thunk.new {|f| f.attribute_sets.fetch(d.fetch('nested-attributes')) },
|
|
163
|
+
)
|
|
164
|
+
when 'sub-message'
|
|
165
|
+
Types::SubMessage.new(
|
|
166
|
+
sub_message: Models::Thunk.new {|f| f.sub_messages.fetch(d.fetch('sub-message')) },
|
|
167
|
+
selector: d.fetch('selector'),
|
|
168
|
+
)
|
|
169
|
+
when 'pad'
|
|
170
|
+
Types::Pad.new(
|
|
171
|
+
length: nil,
|
|
172
|
+
)
|
|
173
|
+
when 'unused'
|
|
174
|
+
nil
|
|
175
|
+
else
|
|
176
|
+
raise ParseError, "Unknown type: #{type}"
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
private def parse_byte_order(v)
|
|
181
|
+
case v
|
|
182
|
+
when nil
|
|
183
|
+
:host
|
|
184
|
+
when 'big-endian'
|
|
185
|
+
:big
|
|
186
|
+
when 'litten-endian'
|
|
187
|
+
:little
|
|
188
|
+
else
|
|
189
|
+
raise ParseError, "Unknown endian: #{v}"
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
private def parse_struct(d)
|
|
194
|
+
result = Models::Struct.new(name: d.fetch('name'), doc: d['doc'])
|
|
195
|
+
|
|
196
|
+
d.fetch('members').each do |v|
|
|
197
|
+
type = parse_struct_member_type(v)
|
|
198
|
+
member = Models::Struct::Member.new(name: v.fetch('name'), type: type, doc: v['doc'])
|
|
199
|
+
result.members << member
|
|
200
|
+
rescue
|
|
201
|
+
raise ParseError, "Failed to parse struct member: #{v.fetch('name')}"
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
result
|
|
205
|
+
rescue
|
|
206
|
+
raise ParseError, "Failed to parse struct: #{d.fetch('name')}"
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
private def parse_checks(d)
|
|
210
|
+
d.map do |op, value|
|
|
211
|
+
parse_check(op, value)
|
|
212
|
+
end
|
|
213
|
+
rescue
|
|
214
|
+
raise ParseError, "Failed to parse checks"
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
private def parse_check(op, value_literal)
|
|
218
|
+
case op
|
|
219
|
+
when 'max'
|
|
220
|
+
value = parse_value(value_literal)
|
|
221
|
+
return %{raise unless it <= #{value}}
|
|
222
|
+
when 'min'
|
|
223
|
+
value = parse_value(value_literal)
|
|
224
|
+
return %{raise unless it >= #{value}}
|
|
225
|
+
when 'min-len'
|
|
226
|
+
value = parse_value(value_literal)
|
|
227
|
+
return %{raise unless it.bytesize >= #{value}}
|
|
228
|
+
when 'max-len'
|
|
229
|
+
value = parse_value(value_literal)
|
|
230
|
+
return %{raise unless it.bytesize <= #{value}}
|
|
231
|
+
else
|
|
232
|
+
raise ParseError, "Unknown check: #{op}"
|
|
233
|
+
end
|
|
234
|
+
rescue
|
|
235
|
+
raise ParseError, "Failed to parse check: #{op}"
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
private def parse_value(v)
|
|
239
|
+
case v
|
|
240
|
+
when Integer
|
|
241
|
+
v
|
|
242
|
+
when 'u32-max'
|
|
243
|
+
(2 ** 32) - 1
|
|
244
|
+
when 's32-max'
|
|
245
|
+
(2 ** 31) - 1
|
|
246
|
+
else
|
|
247
|
+
raise ParseError, "Unknown value: #{v}"
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
private def parse_attribute_set(d)
|
|
252
|
+
name = d.fetch('name')
|
|
253
|
+
if subset_of = d['subset-of']
|
|
254
|
+
result = Models::Thunk.new do |f|
|
|
255
|
+
superset = f.attribute_sets.fetch(subset_of)
|
|
256
|
+
attribute_set = Models::AttributeSet.new(name:, name_prefix: superset.name_prefix, doc: d['doc'])
|
|
257
|
+
d.fetch('attributes').each do |v|
|
|
258
|
+
aname = v.fetch('name')
|
|
259
|
+
sattr = superset.attributes.find {|a| a.name == aname } or raise ParseError, "Attribute not found: #{aname}"
|
|
260
|
+
# TODO: type/checks overrides
|
|
261
|
+
attribute_set.attributes << sattr
|
|
262
|
+
rescue
|
|
263
|
+
raise ParseError, "Failed to parse attribute: #{v.fetch('name')}"
|
|
264
|
+
end
|
|
265
|
+
attribute_set
|
|
266
|
+
end
|
|
267
|
+
else
|
|
268
|
+
name_prefix = d['name_prefix']
|
|
269
|
+
result = Models::AttributeSet.new(name:, name_prefix:, doc: d['doc'])
|
|
270
|
+
value = 0
|
|
271
|
+
|
|
272
|
+
d.fetch('attributes').each do |v|
|
|
273
|
+
if type = parse_attribute_type(v)
|
|
274
|
+
value = v.fetch('value', value + 1)
|
|
275
|
+
attribute = Models::AttributeSet::Attribute.new(name: v.fetch('name'), type: type, value:)
|
|
276
|
+
result.attributes << attribute
|
|
277
|
+
if checks = v['checks']
|
|
278
|
+
attribute.checks = parse_checks(checks)
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
rescue
|
|
282
|
+
raise ParseError, "Failed to parse attribute: #{v.fetch('name')}"
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
@attribute_sets[name] = result
|
|
287
|
+
|
|
288
|
+
rescue
|
|
289
|
+
raise ParseError, "Failed to parse attribute set: #{d.fetch('name')}"
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
private def parse_sub_message(d)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
private def parse_operation(d, enum_model:)
|
|
296
|
+
name = d.fetch('name')
|
|
297
|
+
|
|
298
|
+
fixed_header = d['fixed-header']&.then do
|
|
299
|
+
@structs.fetch(it)
|
|
300
|
+
rescue
|
|
301
|
+
raise ParseError, "Undefined fixed header: #{it}"
|
|
302
|
+
end || @default_fixed_header
|
|
303
|
+
|
|
304
|
+
attribute_set = d['attribute-set']&.then do
|
|
305
|
+
@attribute_sets.fetch(it)
|
|
306
|
+
rescue
|
|
307
|
+
raise ParseError, "Undefined attribute set: #{it}"
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
doit = d['do']&.then { parse_request_reply(it) }
|
|
311
|
+
dumpit = d['dump']&.then { parse_request_reply(it) }
|
|
312
|
+
|
|
313
|
+
@operations[name] = Models::Operation.new(name:, doc: d['doc'], fixed_header:, attribute_set:, doit:, dumpit:)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
private def parse_request_reply(d)
|
|
317
|
+
Models::RequestReply.new(
|
|
318
|
+
request: d['request']&.then { parse_message(it) },
|
|
319
|
+
reply: d['reply']&.then { parse_message(it) },
|
|
320
|
+
)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
private def parse_message(d)
|
|
324
|
+
Models::Message.new(value: d['value'], attributes: d.fetch('attributes', []))
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
private def parse_mcast_group(d)
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
data/lib/ynl.rb
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Parse for YNL netlink specification
|
|
2
|
+
#
|
|
3
|
+
# See: https://www.kernel.org/doc/html/latest/userspace-api/netlink/specs.html
|
|
4
|
+
|
|
5
|
+
require_relative 'ynl/family'
|
|
6
|
+
|
|
7
|
+
module Ynl
|
|
8
|
+
class ParseError < StandardError; end
|
|
9
|
+
|
|
10
|
+
module Types
|
|
11
|
+
Scalar = Data.define(:type, :byte_order) do
|
|
12
|
+
def resolve(f)
|
|
13
|
+
self
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
String = Struct.new do
|
|
17
|
+
def resolve(f)
|
|
18
|
+
self
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
Binary = Struct.new(:struct, :length, :display_hint) do
|
|
22
|
+
def resolve(f)
|
|
23
|
+
self.struct = struct.resolve(f) if self.struct
|
|
24
|
+
self
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
NestedAttributes = Struct.new(:attribute_set) do
|
|
28
|
+
def resolve(f)
|
|
29
|
+
self.attribute_set = attribute_set.resolve(f)
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
SubMessage = Struct.new(:sub_message, :selector) do
|
|
34
|
+
def resolve(f)
|
|
35
|
+
self.sub_message = sub_message.resolve(f)
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
Pad = Data.define(:length) do
|
|
41
|
+
def resolve(f)
|
|
42
|
+
self
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
data/test.rb
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
require 'nl'
|
|
2
|
+
require 'ynl'
|
|
3
|
+
yaml = File.join(__dir__, '../nl-linux/linux/rt_route.yaml')
|
|
4
|
+
cls = Ynl::Family.build(yaml)
|
|
5
|
+
|
|
6
|
+
#p result = cls.open.dump_getaddr(ifa_index: 1)
|
|
7
|
+
p result = cls.open.dump_getroute(rtm_family: 1)
|
|
8
|
+
binding.irb
|
|
9
|
+
|
|
10
|
+
#p cls.open.dump_get_stats
|
metadata
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ynl
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Kasumi Hanazuki
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: nl
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - '='
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.1.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - '='
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.1.0
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: yaml
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
description: Linux Netlink
|
|
41
|
+
email:
|
|
42
|
+
- kasumi@rollingapple.net
|
|
43
|
+
executables: []
|
|
44
|
+
extensions: []
|
|
45
|
+
extra_rdoc_files: []
|
|
46
|
+
files:
|
|
47
|
+
- ".rspec"
|
|
48
|
+
- Rakefile
|
|
49
|
+
- lib/ynl.rb
|
|
50
|
+
- lib/ynl/family.rb
|
|
51
|
+
- lib/ynl/generator.rb
|
|
52
|
+
- lib/ynl/models.rb
|
|
53
|
+
- lib/ynl/parser.rb
|
|
54
|
+
- test.rb
|
|
55
|
+
homepage: https://github.com/hanazuki/nl
|
|
56
|
+
licenses:
|
|
57
|
+
- MIT
|
|
58
|
+
metadata:
|
|
59
|
+
homepage_uri: https://github.com/hanazuki/nl
|
|
60
|
+
source_code_uri: https://github.com/hanazuki/nl
|
|
61
|
+
changelog_uri: https://github.com/hanazuki/nl/blob/master/ynl/CHANGELOG.md
|
|
62
|
+
rdoc_options: []
|
|
63
|
+
require_paths:
|
|
64
|
+
- lib
|
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - ">="
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '3.1'
|
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
requirements: []
|
|
76
|
+
rubygems_version: 3.6.8
|
|
77
|
+
specification_version: 4
|
|
78
|
+
summary: Linux Netlink - core
|
|
79
|
+
test_files: []
|