xref: /linux/tools/net/ynl/pyynl/lib/nlspec.py (revision 14ea4cd1b19162888f629c4ce1ba268c683b0f12)
1# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2
3import collections
4import importlib
5import os
6import yaml
7
8
9# To be loaded dynamically as needed
10jsonschema = None
11
12
13class 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
60class 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
100class 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        return self.has_entry_doc()
135
136    def has_entry_doc(self):
137        for entry in self.entries.values():
138            if entry.has_doc():
139                return True
140        return False
141
142    def get_mask(self, as_flags=None):
143        mask = 0
144        for e in self.entries.values():
145            mask += e.user_value(as_flags)
146        return mask
147
148
149class SpecAttr(SpecElement):
150    """ Single Netlink attribute type
151
152    Represents a single attribute type within an attr space.
153
154    Attributes:
155        type          string, attribute type
156        value         numerical ID when serialized
157        attr_set      Attribute Set containing this attr
158        is_multi      bool, attr may repeat multiple times
159        struct_name   string, name of struct definition
160        sub_type      string, name of sub type
161        len           integer, optional byte length of binary types
162        display_hint  string, hint to help choose format specifier
163                      when displaying the value
164        sub_message   string, name of sub message type
165        selector      string, name of attribute used to select
166                      sub-message type
167
168        is_auto_scalar bool, attr is a variable-size scalar
169    """
170    def __init__(self, family, attr_set, yaml, value):
171        super().__init__(family, yaml)
172
173        self.type = yaml['type']
174        self.value = value
175        self.attr_set = attr_set
176        self.is_multi = yaml.get('multi-attr', False)
177        self.struct_name = yaml.get('struct')
178        self.sub_type = yaml.get('sub-type')
179        self.byte_order = yaml.get('byte-order')
180        self.len = yaml.get('len')
181        self.display_hint = yaml.get('display-hint')
182        self.sub_message = yaml.get('sub-message')
183        self.selector = yaml.get('selector')
184
185        self.is_auto_scalar = self.type == "sint" or self.type == "uint"
186
187
188class SpecAttrSet(SpecElement):
189    """ Netlink Attribute Set class.
190
191    Represents a ID space of attributes within Netlink.
192
193    Note that unlike other elements, which expose contents of the raw spec
194    via the dictionary interface Attribute Set exposes attributes by name.
195
196    Attributes:
197        attrs      ordered dict of all attributes (indexed by name)
198        attrs_by_val  ordered dict of all attributes (indexed by value)
199        subset_of  parent set if this is a subset, otherwise None
200    """
201    def __init__(self, family, yaml):
202        super().__init__(family, yaml)
203
204        self.subset_of = self.yaml.get('subset-of', None)
205
206        self.attrs = collections.OrderedDict()
207        self.attrs_by_val = collections.OrderedDict()
208
209        if self.subset_of is None:
210            val = 1
211            for elem in self.yaml['attributes']:
212                if 'value' in elem:
213                    val = elem['value']
214
215                attr = self.new_attr(elem, val)
216                self.attrs[attr.name] = attr
217                self.attrs_by_val[attr.value] = attr
218                val += 1
219        else:
220            real_set = family.attr_sets[self.subset_of]
221            for elem in self.yaml['attributes']:
222                real_attr = real_set[elem['name']]
223                combined_elem = real_attr.yaml | elem
224                attr = self.new_attr(combined_elem, real_attr.value)
225
226                self.attrs[attr.name] = attr
227                self.attrs_by_val[attr.value] = attr
228
229    def new_attr(self, elem, value):
230        return SpecAttr(self.family, self, elem, value)
231
232    def __getitem__(self, key):
233        return self.attrs[key]
234
235    def __contains__(self, key):
236        return key in self.attrs
237
238    def __iter__(self):
239        yield from self.attrs
240
241    def items(self):
242        return self.attrs.items()
243
244
245class SpecStructMember(SpecElement):
246    """Struct member attribute
247
248    Represents a single struct member attribute.
249
250    Attributes:
251        type        string, type of the member attribute
252        byte_order  string or None for native byte order
253        enum        string, name of the enum definition
254        len         integer, optional byte length of binary types
255        display_hint  string, hint to help choose format specifier
256                      when displaying the value
257        struct      string, name of nested struct type
258    """
259    def __init__(self, family, yaml):
260        super().__init__(family, yaml)
261        self.type = yaml['type']
262        self.byte_order = yaml.get('byte-order')
263        self.enum = yaml.get('enum')
264        self.len = yaml.get('len')
265        self.display_hint = yaml.get('display-hint')
266        self.struct = yaml.get('struct')
267
268
269class SpecStruct(SpecElement):
270    """Netlink struct type
271
272    Represents a C struct definition.
273
274    Attributes:
275        members   ordered list of struct members
276    """
277    def __init__(self, family, yaml):
278        super().__init__(family, yaml)
279
280        self.members = []
281        for member in yaml.get('members', []):
282            self.members.append(self.new_member(family, member))
283
284    def new_member(self, family, elem):
285        return SpecStructMember(family, elem)
286
287    def __iter__(self):
288        yield from self.members
289
290    def items(self):
291        return self.members.items()
292
293
294class SpecSubMessage(SpecElement):
295    """ Netlink sub-message definition
296
297    Represents a set of sub-message formats for polymorphic nlattrs
298    that contain type-specific sub messages.
299
300    Attributes:
301        name     string, name of sub-message definition
302        formats  dict of sub-message formats indexed by match value
303    """
304    def __init__(self, family, yaml):
305        super().__init__(family, yaml)
306
307        self.formats = collections.OrderedDict()
308        for elem in self.yaml['formats']:
309            format = self.new_format(family, elem)
310            self.formats[format.value] = format
311
312    def new_format(self, family, format):
313        return SpecSubMessageFormat(family, format)
314
315
316class SpecSubMessageFormat(SpecElement):
317    """ Netlink sub-message format definition
318
319    Represents a single format for a sub-message.
320
321    Attributes:
322        value         attribute value to match against type selector
323        fixed_header  string, name of fixed header, or None
324        attr_set      string, name of attribute set, or None
325    """
326    def __init__(self, family, yaml):
327        super().__init__(family, yaml)
328
329        self.value = yaml.get('value')
330        self.fixed_header = yaml.get('fixed-header')
331        self.attr_set = yaml.get('attribute-set')
332
333
334class SpecOperation(SpecElement):
335    """Netlink Operation
336
337    Information about a single Netlink operation.
338
339    Attributes:
340        value           numerical ID when serialized, None if req/rsp values differ
341
342        req_value       numerical ID when serialized, user -> kernel
343        rsp_value       numerical ID when serialized, user <- kernel
344        modes           supported operation modes (do, dump, event etc.)
345        is_call         bool, whether the operation is a call
346        is_async        bool, whether the operation is a notification
347        is_resv         bool, whether the operation does not exist (it's just a reserved ID)
348        attr_set        attribute set name
349        fixed_header    string, optional name of fixed header struct
350
351        yaml            raw spec as loaded from the spec file
352    """
353    def __init__(self, family, yaml, req_value, rsp_value):
354        super().__init__(family, yaml)
355
356        self.value = req_value if req_value == rsp_value else None
357        self.req_value = req_value
358        self.rsp_value = rsp_value
359
360        self.modes = yaml.keys() & {'do', 'dump', 'event', 'notify'}
361        self.is_call = 'do' in yaml or 'dump' in yaml
362        self.is_async = 'notify' in yaml or 'event' in yaml
363        self.is_resv = not self.is_async and not self.is_call
364        self.fixed_header = self.yaml.get('fixed-header', family.fixed_header)
365
366        # Added by resolve:
367        self.attr_set = None
368        delattr(self, "attr_set")
369
370    def resolve(self):
371        self.resolve_up(super())
372
373        if 'attribute-set' in self.yaml:
374            attr_set_name = self.yaml['attribute-set']
375        elif 'notify' in self.yaml:
376            msg = self.family.msgs[self.yaml['notify']]
377            attr_set_name = msg['attribute-set']
378        elif self.is_resv:
379            attr_set_name = ''
380        else:
381            raise Exception(f"Can't resolve attribute set for op '{self.name}'")
382        if attr_set_name:
383            self.attr_set = self.family.attr_sets[attr_set_name]
384
385
386class SpecMcastGroup(SpecElement):
387    """Netlink Multicast Group
388
389    Information about a multicast group.
390
391    Value is only used for classic netlink families that use the
392    netlink-raw schema. Genetlink families use dynamic ID allocation
393    where the ids of multicast groups get resolved at runtime. Value
394    will be None for genetlink families.
395
396    Attributes:
397        name      name of the mulitcast group
398        value     integer id of this multicast group for netlink-raw or None
399        yaml      raw spec as loaded from the spec file
400    """
401    def __init__(self, family, yaml):
402        super().__init__(family, yaml)
403        self.value = self.yaml.get('value')
404
405
406class SpecFamily(SpecElement):
407    """ Netlink Family Spec class.
408
409    Netlink family information loaded from a spec (e.g. in YAML).
410    Takes care of unfolding implicit information which can be skipped
411    in the spec itself for brevity.
412
413    The class can be used like a dictionary to access the raw spec
414    elements but that's usually a bad idea.
415
416    Attributes:
417        proto     protocol type (e.g. genetlink)
418        msg_id_model   enum-model for operations (unified, directional etc.)
419        license   spec license (loaded from an SPDX tag on the spec)
420
421        attr_sets  dict of attribute sets
422        msgs       dict of all messages (index by name)
423        sub_msgs   dict of all sub messages (index by name)
424        ops        dict of all valid requests / responses
425        ntfs       dict of all async events
426        consts     dict of all constants/enums
427        fixed_header  string, optional name of family default fixed header struct
428        mcast_groups  dict of all multicast groups (index by name)
429        kernel_family   dict of kernel family attributes
430    """
431    def __init__(self, spec_path, schema_path=None, exclude_ops=None):
432        with open(spec_path, "r") as stream:
433            prefix = '# SPDX-License-Identifier: '
434            first = stream.readline().strip()
435            if not first.startswith(prefix):
436                raise Exception('SPDX license tag required in the spec')
437            self.license = first[len(prefix):]
438
439            stream.seek(0)
440            spec = yaml.safe_load(stream)
441
442        self._resolution_list = []
443
444        super().__init__(self, spec)
445
446        self._exclude_ops = exclude_ops if exclude_ops else []
447
448        self.proto = self.yaml.get('protocol', 'genetlink')
449        self.msg_id_model = self.yaml['operations'].get('enum-model', 'unified')
450
451        if schema_path is None:
452            schema_path = os.path.dirname(os.path.dirname(spec_path)) + f'/{self.proto}.yaml'
453        if schema_path:
454            global jsonschema
455
456            with open(schema_path, "r") as stream:
457                schema = yaml.safe_load(stream)
458
459            if jsonschema is None:
460                jsonschema = importlib.import_module("jsonschema")
461
462            jsonschema.validate(self.yaml, schema)
463
464        self.attr_sets = collections.OrderedDict()
465        self.sub_msgs = collections.OrderedDict()
466        self.msgs = collections.OrderedDict()
467        self.req_by_value = collections.OrderedDict()
468        self.rsp_by_value = collections.OrderedDict()
469        self.ops = collections.OrderedDict()
470        self.ntfs = collections.OrderedDict()
471        self.consts = collections.OrderedDict()
472        self.mcast_groups = collections.OrderedDict()
473        self.kernel_family = collections.OrderedDict(self.yaml.get('kernel-family', {}))
474
475        last_exception = None
476        while len(self._resolution_list) > 0:
477            resolved = []
478            unresolved = self._resolution_list
479            self._resolution_list = []
480
481            for elem in unresolved:
482                try:
483                    elem.resolve()
484                except (KeyError, AttributeError) as e:
485                    self._resolution_list.append(elem)
486                    last_exception = e
487                    continue
488
489                resolved.append(elem)
490
491            if len(resolved) == 0:
492                raise last_exception
493
494    def new_enum(self, elem):
495        return SpecEnumSet(self, elem)
496
497    def new_attr_set(self, elem):
498        return SpecAttrSet(self, elem)
499
500    def new_struct(self, elem):
501        return SpecStruct(self, elem)
502
503    def new_sub_message(self, elem):
504        return SpecSubMessage(self, elem);
505
506    def new_operation(self, elem, req_val, rsp_val):
507        return SpecOperation(self, elem, req_val, rsp_val)
508
509    def new_mcast_group(self, elem):
510        return SpecMcastGroup(self, elem)
511
512    def add_unresolved(self, elem):
513        self._resolution_list.append(elem)
514
515    def _dictify_ops_unified(self):
516        self.fixed_header = self.yaml['operations'].get('fixed-header')
517        val = 1
518        for elem in self.yaml['operations']['list']:
519            if 'value' in elem:
520                val = elem['value']
521
522            op = self.new_operation(elem, val, val)
523            val += 1
524
525            self.msgs[op.name] = op
526
527    def _dictify_ops_directional(self):
528        self.fixed_header = self.yaml['operations'].get('fixed-header')
529        req_val = rsp_val = 1
530        for elem in self.yaml['operations']['list']:
531            if 'notify' in elem or 'event' in elem:
532                if 'value' in elem:
533                    rsp_val = elem['value']
534                req_val_next = req_val
535                rsp_val_next = rsp_val + 1
536                req_val = None
537            elif 'do' in elem or 'dump' in elem:
538                mode = elem['do'] if 'do' in elem else elem['dump']
539
540                v = mode.get('request', {}).get('value', None)
541                if v:
542                    req_val = v
543                v = mode.get('reply', {}).get('value', None)
544                if v:
545                    rsp_val = v
546
547                rsp_inc = 1 if 'reply' in mode else 0
548                req_val_next = req_val + 1
549                rsp_val_next = rsp_val + rsp_inc
550            else:
551                raise Exception("Can't parse directional ops")
552
553            if req_val == req_val_next:
554                req_val = None
555            if rsp_val == rsp_val_next:
556                rsp_val = None
557
558            skip = False
559            for exclude in self._exclude_ops:
560                skip |= bool(exclude.match(elem['name']))
561            if not skip:
562                op = self.new_operation(elem, req_val, rsp_val)
563
564            req_val = req_val_next
565            rsp_val = rsp_val_next
566
567            self.msgs[op.name] = op
568
569    def find_operation(self, name):
570      """
571      For a given operation name, find and return operation spec.
572      """
573      for op in self.yaml['operations']['list']:
574        if name == op['name']:
575          return op
576      return None
577
578    def resolve(self):
579        self.resolve_up(super())
580
581        definitions = self.yaml.get('definitions', [])
582        for elem in definitions:
583            if elem['type'] == 'enum' or elem['type'] == 'flags':
584                self.consts[elem['name']] = self.new_enum(elem)
585            elif elem['type'] == 'struct':
586                self.consts[elem['name']] = self.new_struct(elem)
587            else:
588                self.consts[elem['name']] = elem
589
590        for elem in self.yaml['attribute-sets']:
591            attr_set = self.new_attr_set(elem)
592            self.attr_sets[elem['name']] = attr_set
593
594        for elem in self.yaml.get('sub-messages', []):
595            sub_message = self.new_sub_message(elem)
596            self.sub_msgs[sub_message.name] = sub_message
597
598        if self.msg_id_model == 'unified':
599            self._dictify_ops_unified()
600        elif self.msg_id_model == 'directional':
601            self._dictify_ops_directional()
602
603        for op in self.msgs.values():
604            if op.req_value is not None:
605                self.req_by_value[op.req_value] = op
606            if op.rsp_value is not None:
607                self.rsp_by_value[op.rsp_value] = op
608            if not op.is_async and 'attribute-set' in op:
609                self.ops[op.name] = op
610            elif op.is_async:
611                self.ntfs[op.name] = op
612
613        mcgs = self.yaml.get('mcast-groups')
614        if mcgs:
615            for elem in mcgs['list']:
616                mcg = self.new_mcast_group(elem)
617                self.mcast_groups[elem['name']] = mcg
618