Subject: [RFC PATCH net-next v1 3/6] tools/net/ynl: Add dynamic attribute decoding to ynl

From: Donald Hunter <[email-masked]>
Date: Wed, 29 Nov 2023 10:11:56 +0000

Implement dynamic attribute space selection and decoding to
the ynl library.

Encode support is not yet implemented. Support for dynamic selectors
at a different nest level from the key attribute is not yet supported.

Signed-off-by: Donald Hunter <[email-masked]>
---
 tools/net/ynl/lib/nlspec.py | 27 +++++++++++++++++++++++++++
 tools/net/ynl/lib/ynl.py    | 32 ++++++++++++++++++++++++++++++++
 2 files changed, 59 insertions(+)

diff --git a/tools/net/ynl/lib/nlspec.py b/tools/net/ynl/lib/nlspec.py
index 92889298b197..c32c85ddf8fb 100644
--- a/tools/net/ynl/lib/nlspec.py
+++ b/tools/net/ynl/lib/nlspec.py
@@ -142,6 +142,29 @@ class SpecEnumSet(SpecElement):
             mask += e.user_value(as_flags)
         return mask
 
+class SpecDynAttr(SpecElement):
+    """ Single Dynamic Netlink atttribute type
+
+    Represents a choice of dynamic attribute type within an attr space.
+
+    Attributes:
+        value         attribute value to match against dynamic type selector
+        struct_name   string, name of struct definition
+        sub_type      string, name of sub type
+        len           integer, optional byte length of binary types
+        display_hint  string, hint to help choose format specifier
+                      when displaying the value
+    """
+    def __init__(self, family, parent, yaml):
+        super().__init__(family, yaml)
+
+        self.value = yaml.get('value')
+        self.struct_name = yaml.get('struct')
+        self.sub_type = yaml.get('sub-type')
+        self.byte_order = yaml.get('byte-order')
+        self.len = yaml.get('len')
+        self.display_hint = yaml.get('display-hint')
+
 
 class SpecAttr(SpecElement):
     """ Single Netlink atttribute type
@@ -173,9 +196,13 @@ class SpecAttr(SpecElement):
         self.byte_order = yaml.get('byte-order')
         self.len = yaml.get('len')
         self.display_hint = yaml.get('display-hint')
+        self.dynamic_types = {}
 
         self.is_auto_scalar = self.type == "sint" or self.type == "uint"
 
+        if 'selector' in yaml:
+            for item in yaml.get('selector').get('list', []):
+                self.dynamic_types[item['value']] = SpecDynAttr(family, self, item)
 
 class SpecAttrSet(SpecElement):
     """ Netlink Attribute Set class.
diff --git a/tools/net/ynl/lib/ynl.py b/tools/net/ynl/lib/ynl.py
index 92995bca14e1..5ce01ce37573 100644
--- a/tools/net/ynl/lib/ynl.py
+++ b/tools/net/ynl/lib/ynl.py
@@ -549,6 +549,36 @@ class YnlFamily(SpecFamily):
         else:
             rsp[name] = [decoded]
 
+    def _resolve_selector(self, attr_spec, vals):
+        if 'selector' in attr_spec:
+            selector = attr_spec['selector']
+            key = selector['attribute']
+            if key in vals:
+                value = vals[key]
+                if value in attr_spec.dynamic_types:
+                    spec = attr_spec.dynamic_types[value]
+                    return spec
+                else:
+                    raise Exception(f"No entry for {key}={value} in selector for '{attr_spec['name']}'")
+            else:
+                raise Exception(f"There is no value for {key} to use in selector for '{attr_spec['name']}'")
+        else:
+            raise Exception("type=dynamic requires a selector in '{attr_spec['name']}'")
+
+    def _decode_dynamic(self, attr, attr_spec, rsp):
+        dyn_spec = self._resolve_selector(attr_spec, rsp)
+        if dyn_spec['type'] == 'binary':
+            decoded = self._decode_binary(attr, dyn_spec)
+        elif dyn_spec['type'] == 'nest':
+            attr_space = dyn_spec['nested-attributes']
+            if attr_space in self.attr_sets:
+                decoded = self._decode(NlAttrs(attr.raw), attr_space)
+            else:
+                raise Exception(f"Unknown attribute-set '{attr_space}'")
+        else:
+            raise Exception(f"Unknown type '{spec['type']}' for value '{value}'")
+        return decoded
+
     def _decode(self, attrs, space):
         if space:
             attr_space = self.attr_sets[space]
@@ -586,6 +616,8 @@ class YnlFamily(SpecFamily):
                     value = self._decode_enum(value, attr_spec)
                     selector = self._decode_enum(selector, attr_spec)
                 decoded = {"value": value, "selector": selector}
+            elif attr_spec["type"] == 'dynamic':
+                decoded = self._decode_dynamic(attr, attr_spec, rsp)
             else:
                 if not self.process_unknown:
                     raise Exception(f'Unknown {attr_spec["type"]} with name {attr_spec["name"]}')
-- 
2.42.0