tools: ynl-gen: sanitize notification tracking
[platform/kernel/linux-rpi.git] / tools / net / ynl / lib / nlspec.py
1 # SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2
3 import collections
4 import importlib
5 import os
6 import yaml
7
8
9 # To be loaded dynamically as needed
10 jsonschema = None
11
12
13 class SpecElement:
14     """Netlink spec element.
15
16     Abstract element of the Netlink spec. Implements the dictionary interface
17     for access to the raw spec. Supports iterative resolution of dependencies
18     across elements and class inheritance levels. The elements of the spec
19     may refer to each other, and although loops should be very rare, having
20     to maintain correct ordering of instantiation is painful, so the resolve()
21     method should be used to perform parts of init which require access to
22     other parts of the spec.
23
24     Attributes:
25         yaml        raw spec as loaded from the spec file
26         family      back reference to the full family
27
28         name        name of the entity as listed in the spec (optional)
29         ident_name  name which can be safely used as identifier in code (optional)
30     """
31     def __init__(self, family, yaml):
32         self.yaml = yaml
33         self.family = family
34
35         if 'name' in self.yaml:
36             self.name = self.yaml['name']
37             self.ident_name = self.name.replace('-', '_')
38
39         self._super_resolved = False
40         family.add_unresolved(self)
41
42     def __getitem__(self, key):
43         return self.yaml[key]
44
45     def __contains__(self, key):
46         return key in self.yaml
47
48     def get(self, key, default=None):
49         return self.yaml.get(key, default)
50
51     def resolve_up(self, up):
52         if not self._super_resolved:
53             up.resolve()
54             self._super_resolved = True
55
56     def resolve(self):
57         pass
58
59
60 class SpecEnumEntry(SpecElement):
61     """ Entry within an enum declared in the Netlink spec.
62
63     Attributes:
64         doc         documentation string
65         enum_set    back reference to the enum
66         value       numerical value of this enum (use accessors in most situations!)
67
68     Methods:
69         raw_value   raw value, i.e. the id in the enum, unlike user value which is a mask for flags
70         user_value   user value, same as raw value for enums, for flags it's the mask
71     """
72     def __init__(self, enum_set, yaml, prev, value_start):
73         if isinstance(yaml, str):
74             yaml = {'name': yaml}
75         super().__init__(enum_set.family, yaml)
76
77         self.doc = yaml.get('doc', '')
78         self.enum_set = enum_set
79
80         if 'value' in yaml:
81             self.value = yaml['value']
82         elif prev:
83             self.value = prev.value + 1
84         else:
85             self.value = value_start
86
87     def has_doc(self):
88         return bool(self.doc)
89
90     def raw_value(self):
91         return self.value
92
93     def user_value(self, as_flags=None):
94         if self.enum_set['type'] == 'flags' or as_flags:
95             return 1 << self.value
96         else:
97             return self.value
98
99
100 class SpecEnumSet(SpecElement):
101     """ Enum type
102
103     Represents an enumeration (list of numerical constants)
104     as declared in the "definitions" section of the spec.
105
106     Attributes:
107         type            enum or flags
108         entries         entries by name
109         entries_by_val  entries by value
110     Methods:
111         get_mask      for flags compute the mask of all defined values
112     """
113     def __init__(self, family, yaml):
114         super().__init__(family, yaml)
115
116         self.type = yaml['type']
117
118         prev_entry = None
119         value_start = self.yaml.get('value-start', 0)
120         self.entries = dict()
121         self.entries_by_val = dict()
122         for entry in self.yaml['entries']:
123             e = self.new_entry(entry, prev_entry, value_start)
124             self.entries[e.name] = e
125             self.entries_by_val[e.raw_value()] = e
126             prev_entry = e
127
128     def new_entry(self, entry, prev_entry, value_start):
129         return SpecEnumEntry(self, entry, prev_entry, value_start)
130
131     def has_doc(self):
132         if 'doc' in self.yaml:
133             return True
134         for entry in self.entries.values():
135             if entry.has_doc():
136                 return True
137         return False
138
139     def get_mask(self, as_flags=None):
140         mask = 0
141         for e in self.entries.values():
142             mask += e.user_value(as_flags)
143         return mask
144
145
146 class SpecAttr(SpecElement):
147     """ Single Netlink atttribute type
148
149     Represents a single attribute type within an attr space.
150
151     Attributes:
152         value         numerical ID when serialized
153         attr_set      Attribute Set containing this attr
154         is_multi      bool, attr may repeat multiple times
155         struct_name   string, name of struct definition
156         sub_type      string, name of sub type
157     """
158     def __init__(self, family, attr_set, yaml, value):
159         super().__init__(family, yaml)
160
161         self.value = value
162         self.attr_set = attr_set
163         self.is_multi = yaml.get('multi-attr', False)
164         self.struct_name = yaml.get('struct')
165         self.sub_type = yaml.get('sub-type')
166         self.byte_order = yaml.get('byte-order')
167
168
169 class SpecAttrSet(SpecElement):
170     """ Netlink Attribute Set class.
171
172     Represents a ID space of attributes within Netlink.
173
174     Note that unlike other elements, which expose contents of the raw spec
175     via the dictionary interface Attribute Set exposes attributes by name.
176
177     Attributes:
178         attrs      ordered dict of all attributes (indexed by name)
179         attrs_by_val  ordered dict of all attributes (indexed by value)
180         subset_of  parent set if this is a subset, otherwise None
181     """
182     def __init__(self, family, yaml):
183         super().__init__(family, yaml)
184
185         self.subset_of = self.yaml.get('subset-of', None)
186
187         self.attrs = collections.OrderedDict()
188         self.attrs_by_val = collections.OrderedDict()
189
190         if self.subset_of is None:
191             val = 1
192             for elem in self.yaml['attributes']:
193                 if 'value' in elem:
194                     val = elem['value']
195
196                 attr = self.new_attr(elem, val)
197                 self.attrs[attr.name] = attr
198                 self.attrs_by_val[attr.value] = attr
199                 val += 1
200         else:
201             real_set = family.attr_sets[self.subset_of]
202             for elem in self.yaml['attributes']:
203                 attr = real_set[elem['name']]
204                 self.attrs[attr.name] = attr
205                 self.attrs_by_val[attr.value] = attr
206
207     def new_attr(self, elem, value):
208         return SpecAttr(self.family, self, elem, value)
209
210     def __getitem__(self, key):
211         return self.attrs[key]
212
213     def __contains__(self, key):
214         return key in self.attrs
215
216     def __iter__(self):
217         yield from self.attrs
218
219     def items(self):
220         return self.attrs.items()
221
222
223 class SpecStructMember(SpecElement):
224     """Struct member attribute
225
226     Represents a single struct member attribute.
227
228     Attributes:
229         type        string, type of the member attribute
230         byte_order  string or None for native byte order
231         enum        string, name of the enum definition
232     """
233     def __init__(self, family, yaml):
234         super().__init__(family, yaml)
235         self.type = yaml['type']
236         self.byte_order = yaml.get('byte-order')
237         self.enum = yaml.get('enum')
238
239
240 class SpecStruct(SpecElement):
241     """Netlink struct type
242
243     Represents a C struct definition.
244
245     Attributes:
246         members   ordered list of struct members
247     """
248     def __init__(self, family, yaml):
249         super().__init__(family, yaml)
250
251         self.members = []
252         for member in yaml.get('members', []):
253             self.members.append(self.new_member(family, member))
254
255     def new_member(self, family, elem):
256         return SpecStructMember(family, elem)
257
258     def __iter__(self):
259         yield from self.members
260
261     def items(self):
262         return self.members.items()
263
264
265 class SpecOperation(SpecElement):
266     """Netlink Operation
267
268     Information about a single Netlink operation.
269
270     Attributes:
271         value           numerical ID when serialized, None if req/rsp values differ
272
273         req_value       numerical ID when serialized, user -> kernel
274         rsp_value       numerical ID when serialized, user <- kernel
275         is_call         bool, whether the operation is a call
276         is_async        bool, whether the operation is a notification
277         is_resv         bool, whether the operation does not exist (it's just a reserved ID)
278         attr_set        attribute set name
279         fixed_header    string, optional name of fixed header struct
280
281         yaml            raw spec as loaded from the spec file
282     """
283     def __init__(self, family, yaml, req_value, rsp_value):
284         super().__init__(family, yaml)
285
286         self.value = req_value if req_value == rsp_value else None
287         self.req_value = req_value
288         self.rsp_value = rsp_value
289
290         self.is_call = 'do' in yaml or 'dump' in yaml
291         self.is_async = 'notify' in yaml or 'event' in yaml
292         self.is_resv = not self.is_async and not self.is_call
293         self.fixed_header = self.yaml.get('fixed-header', family.fixed_header)
294
295         # Added by resolve:
296         self.attr_set = None
297         delattr(self, "attr_set")
298
299     def resolve(self):
300         self.resolve_up(super())
301
302         if 'attribute-set' in self.yaml:
303             attr_set_name = self.yaml['attribute-set']
304         elif 'notify' in self.yaml:
305             msg = self.family.msgs[self.yaml['notify']]
306             attr_set_name = msg['attribute-set']
307         elif self.is_resv:
308             attr_set_name = ''
309         else:
310             raise Exception(f"Can't resolve attribute set for op '{self.name}'")
311         if attr_set_name:
312             self.attr_set = self.family.attr_sets[attr_set_name]
313
314
315 class SpecFamily(SpecElement):
316     """ Netlink Family Spec class.
317
318     Netlink family information loaded from a spec (e.g. in YAML).
319     Takes care of unfolding implicit information which can be skipped
320     in the spec itself for brevity.
321
322     The class can be used like a dictionary to access the raw spec
323     elements but that's usually a bad idea.
324
325     Attributes:
326         proto     protocol type (e.g. genetlink)
327         msg_id_model   enum-model for operations (unified, directional etc.)
328         license   spec license (loaded from an SPDX tag on the spec)
329
330         attr_sets  dict of attribute sets
331         msgs       dict of all messages (index by name)
332         ops        dict of all valid requests / responses
333         ntfs       dict of all async events
334         consts     dict of all constants/enums
335         fixed_header  string, optional name of family default fixed header struct
336     """
337     def __init__(self, spec_path, schema_path=None):
338         with open(spec_path, "r") as stream:
339             prefix = '# SPDX-License-Identifier: '
340             first = stream.readline().strip()
341             if not first.startswith(prefix):
342                 raise Exception('SPDX license tag required in the spec')
343             self.license = first[len(prefix):]
344
345             stream.seek(0)
346             spec = yaml.safe_load(stream)
347
348         self._resolution_list = []
349
350         super().__init__(self, spec)
351
352         self.proto = self.yaml.get('protocol', 'genetlink')
353         self.msg_id_model = self.yaml['operations'].get('enum-model', 'unified')
354
355         if schema_path is None:
356             schema_path = os.path.dirname(os.path.dirname(spec_path)) + f'/{self.proto}.yaml'
357         if schema_path:
358             global jsonschema
359
360             with open(schema_path, "r") as stream:
361                 schema = yaml.safe_load(stream)
362
363             if jsonschema is None:
364                 jsonschema = importlib.import_module("jsonschema")
365
366             jsonschema.validate(self.yaml, schema)
367
368         self.attr_sets = collections.OrderedDict()
369         self.msgs = collections.OrderedDict()
370         self.req_by_value = collections.OrderedDict()
371         self.rsp_by_value = collections.OrderedDict()
372         self.ops = collections.OrderedDict()
373         self.ntfs = collections.OrderedDict()
374         self.consts = collections.OrderedDict()
375
376         last_exception = None
377         while len(self._resolution_list) > 0:
378             resolved = []
379             unresolved = self._resolution_list
380             self._resolution_list = []
381
382             for elem in unresolved:
383                 try:
384                     elem.resolve()
385                 except (KeyError, AttributeError) as e:
386                     self._resolution_list.append(elem)
387                     last_exception = e
388                     continue
389
390                 resolved.append(elem)
391
392             if len(resolved) == 0:
393                 raise last_exception
394
395     def new_enum(self, elem):
396         return SpecEnumSet(self, elem)
397
398     def new_attr_set(self, elem):
399         return SpecAttrSet(self, elem)
400
401     def new_struct(self, elem):
402         return SpecStruct(self, elem)
403
404     def new_operation(self, elem, req_val, rsp_val):
405         return SpecOperation(self, elem, req_val, rsp_val)
406
407     def add_unresolved(self, elem):
408         self._resolution_list.append(elem)
409
410     def _dictify_ops_unified(self):
411         self.fixed_header = self.yaml['operations'].get('fixed-header')
412         val = 1
413         for elem in self.yaml['operations']['list']:
414             if 'value' in elem:
415                 val = elem['value']
416
417             op = self.new_operation(elem, val, val)
418             val += 1
419
420             self.msgs[op.name] = op
421
422     def _dictify_ops_directional(self):
423         self.fixed_header = self.yaml['operations'].get('fixed-header')
424         req_val = rsp_val = 1
425         for elem in self.yaml['operations']['list']:
426             if 'notify' in elem:
427                 if 'value' in elem:
428                     rsp_val = elem['value']
429                 req_val_next = req_val
430                 rsp_val_next = rsp_val + 1
431                 req_val = None
432             elif 'do' in elem or 'dump' in elem:
433                 mode = elem['do'] if 'do' in elem else elem['dump']
434
435                 v = mode.get('request', {}).get('value', None)
436                 if v:
437                     req_val = v
438                 v = mode.get('reply', {}).get('value', None)
439                 if v:
440                     rsp_val = v
441
442                 rsp_inc = 1 if 'reply' in mode else 0
443                 req_val_next = req_val + 1
444                 rsp_val_next = rsp_val + rsp_inc
445             else:
446                 raise Exception("Can't parse directional ops")
447
448             if req_val == req_val_next:
449                 req_val = None
450             if rsp_val == rsp_val_next:
451                 rsp_val = None
452             op = self.new_operation(elem, req_val, rsp_val)
453             req_val = req_val_next
454             rsp_val = rsp_val_next
455
456             self.msgs[op.name] = op
457
458     def find_operation(self, name):
459       """
460       For a given operation name, find and return operation spec.
461       """
462       for op in self.yaml['operations']['list']:
463         if name == op['name']:
464           return op
465       return None
466
467     def resolve(self):
468         self.resolve_up(super())
469
470         definitions = self.yaml.get('definitions', [])
471         for elem in definitions:
472             if elem['type'] == 'enum' or elem['type'] == 'flags':
473                 self.consts[elem['name']] = self.new_enum(elem)
474             elif elem['type'] == 'struct':
475                 self.consts[elem['name']] = self.new_struct(elem)
476             else:
477                 self.consts[elem['name']] = elem
478
479         for elem in self.yaml['attribute-sets']:
480             attr_set = self.new_attr_set(elem)
481             self.attr_sets[elem['name']] = attr_set
482
483         if self.msg_id_model == 'unified':
484             self._dictify_ops_unified()
485         elif self.msg_id_model == 'directional':
486             self._dictify_ops_directional()
487
488         for op in self.msgs.values():
489             if op.req_value is not None:
490                 self.req_by_value[op.req_value] = op
491             if op.rsp_value is not None:
492                 self.rsp_by_value[op.rsp_value] = op
493             if not op.is_async and 'attribute-set' in op:
494                 self.ops[op.name] = op
495             elif op.is_async:
496                 self.ntfs[op.name] = op