API: Add support for limits to language. 24/19124/3
authorOle Troan <ot@cisco.com>
Tue, 23 Apr 2019 15:11:01 +0000 (17:11 +0200)
committerAndrew Yourtchenko <ayourtch@gmail.com>
Mon, 29 Apr 2019 12:07:02 +0000 (12:07 +0000)
string name [limit = 64];

Meta-data to do argument validation.

Change-Id: I1f3e0f09b2d5285224399413d25206f77bd3f4b1
Signed-off-by: Ole Troan <ot@cisco.com>
src/tools/vppapigen/vppapigen.py
src/tools/vppapigen/vppapigen_json.py
src/vpp-api/python/vpp_papi/tests/test_vpp_serializer.py
src/vpp-api/python/vpp_papi/vpp_serializer.py
src/vpp-api/vapi/vapi_json_parser.py
src/vpp/api/vpe.api

index c2f221b..ae2b0b1 100755 (executable)
@@ -298,10 +298,11 @@ class Array():
 
 
 class Field():
-    def __init__(self, fieldtype, name):
+    def __init__(self, fieldtype, name, limit=None):
         self.type = 'Field'
         self.fieldtype = fieldtype
         self.fieldname = name
+        self.limit = limit
 
     def __repr__(self):
         return str([self.fieldtype, self.fieldname])
@@ -504,7 +505,7 @@ class VPPAPIParser(object):
 
     def p_enum_statements(self, p):
         '''enum_statements : enum_statement
-                            | enum_statements enum_statement'''
+                           | enum_statements enum_statement'''
         if len(p) == 2:
             p[0] = [p[1]]
         else:
@@ -519,11 +520,16 @@ class VPPAPIParser(object):
             p[0] = p[1]
 
     def p_declaration(self, p):
-        '''declaration : type_specifier ID ';' '''
-        if len(p) != 4:
+        '''declaration : type_specifier ID ';'
+                       | type_specifier ID '[' ID '=' assignee ']' ';' '''
+        if len(p) == 9:
+            p[0] = Field(p[1], p[2], {p[4]: p[6]})
+        elif len(p) == 4:
+            p[0] = Field(p[1], p[2])
+        else:
             self._parse_error('ERROR')
         self.fields.append(p[2])
-        p[0] = Field(p[1], p[2])
+
 
     def p_declaration_array(self, p):
         '''declaration : type_specifier ID '[' NUM ']' ';'
index c563a08..94a9e19 100644 (file)
@@ -33,7 +33,10 @@ def walk_defs(s):
         d.append(t.name)
         for b in t.block:
             if b.type == 'Field':
-                d.append([b.fieldtype, b.fieldname])
+                if b.limit:
+                    d.append([b.fieldtype, b.fieldname, b.limit])
+                else:
+                    d.append([b.fieldtype, b.fieldname])
             elif b.type == 'Array':
                 if b.lengthfield:
                     d.append([b.fieldtype, b.fieldname, b.length, b.lengthfield])
index 4fbda2a..ec73347 100755 (executable)
@@ -3,12 +3,27 @@
 import unittest
 from vpp_papi.vpp_serializer import VPPType, VPPEnumType
 from vpp_papi.vpp_serializer import VPPUnionType, VPPMessage
-from vpp_papi.vpp_serializer import VPPTypeAlias
+from vpp_papi.vpp_serializer import VPPTypeAlias, VPPSerializerValueError
 from socket import inet_pton, AF_INET, AF_INET6
 import logging
 import sys
 from ipaddress import *
 
+class TestLimits(unittest.TestCase):
+    def test_limit(self):
+        limited_type = VPPType('limited_type_t',
+                               [['string', 'name', {'limit': 16}]])
+        unlimited_type = VPPType('limited_type_t',
+                                 [['string', 'name']])
+
+
+        b = limited_type.pack({'name':'foobar'})
+        self.assertEqual(len(b), 10)
+        b = unlimited_type.pack({'name':'foobar'})
+        self.assertEqual(len(b), 10)
+
+        with self.assertRaises(VPPSerializerValueError):
+            b = limited_type.pack({'name':'foobar'*3})
 
 class TestAddType(unittest.TestCase):
 
index bd5f050..d109331 100644 (file)
@@ -96,14 +96,20 @@ class BaseTypes(object):
 
 
 class String(object):
-    def __init__(self):
+    def __init__(self, options):
         self.name = 'string'
         self.size = 1
         self.length_field_packer = BaseTypes('u32')
+        self.limit = options['limit'] if 'limit' in options else None
 
     def pack(self, list, kwargs=None):
         if not list:
             return self.length_field_packer.pack(0) + b""
+        if self.limit and len(list) > self.limit:
+            raise VPPSerializerValueError(
+                "Invalid argument length for: {}, {} maximum {}".
+                format(list, len(list), self.limit))
+
         return self.length_field_packer.pack(len(list)) + list.encode('utf8')
 
     def unpack(self, data, offset=0, result=None, ntc=False):
@@ -120,7 +126,7 @@ class String(object):
 types = {'u8': BaseTypes('u8'), 'u16': BaseTypes('u16'),
          'u32': BaseTypes('u32'), 'i32': BaseTypes('i32'),
          'u64': BaseTypes('u64'), 'f64': BaseTypes('f64'),
-         'bool': BaseTypes('bool'), 'string': String()}
+         'bool': BaseTypes('bool'), 'string': String}
 
 
 def vpp_get_type(name):
@@ -416,7 +422,15 @@ class VPPType(object):
                 logger.debug('Unknown type {}'.format(f_type))
                 raise VPPSerializerValueError(
                     'Unknown message type {}'.format(f_type))
-            if len(f) == 3:  # list
+
+            l = len(f)
+            options = [x for x in f if type(x) is dict]
+            if len(options):
+                self.options = options[0]
+                l -= 1
+            else:
+                self.options = {}
+            if l == 3:  # list
                 list_elements = f[2]
                 if list_elements == 0:
                     p = VLAList_legacy(f_name, f_type)
@@ -429,13 +443,17 @@ class VPPType(object):
                     p = FixedList(f_name, f_type, list_elements)
                     self.packers.append(p)
                     size += p.size
-            elif len(f) == 4:  # Variable length list
+            elif l == 4:  # Variable length list
                 length_index = self.fields.index(f[3])
                 p = VLAList(f_name, f_type, f[3], length_index)
                 self.packers.append(p)
             else:
-                self.packers.append(types[f_type])
-                size += types[f_type].size
+                if f_type == 'string':
+                    p = types[f_type](self.options)
+                else:
+                    p = types[f_type]
+                self.packers.append(p)
+                size += p.size
 
         self.size = size
         self.tuple = collections.namedtuple(name, self.fields, rename=True)
index fda3f75..fbeb188 100644 (file)
@@ -167,13 +167,16 @@ class Message(object):
             else:
                 field_type = json_parser.lookup_type_like_id(field[0])
                 logger.debug("Parsing message field `%s'" % field)
-                if len(field) == 2:
+                l = len(field)
+                if any(type(n) is dict for n in field):
+                    l -= 1
+                if l == 2:
                     if self.header is not None and\
                             self.header.has_field(field[1]):
                         continue
                     p = field_class(field_name=field[1],
                                     field_type=field_type)
-                elif len(field) == 3:
+                elif l == 3:
                     if field[2] == 0:
                         raise ParseError(
                             "While parsing message `%s': variable length "
@@ -184,7 +187,7 @@ class Message(object):
                         field_name=field[1],
                         field_type=field_type,
                         array_len=field[2])
-                elif len(field) == 4:
+                elif l == 4:
                     nelem_field = None
                     for f in fields:
                         if f.name == field[3]:
index a2a6779..94732bb 100644 (file)
@@ -183,10 +183,10 @@ define show_version_reply
 {
   u32 context;
   i32 retval;
-  string program;
-  string version;
-  string build_date;
-  string build_directory;
+  string program [limit = 32];
+  string version [limit = 32];
+  string build_date [limit = 32];
+  string build_directory [limit = 256];
 };