papi: export packed message structures
[vpp.git] / src / vpp-api / python / vpp_papi / vpp_papi.py
index e22b88b..e67ee19 100644 (file)
@@ -30,13 +30,14 @@ import fnmatch
 import weakref
 import atexit
 import time
 import weakref
 import atexit
 import time
-from . vpp_format import verify_enum_hint
-from . vpp_serializer import VPPType, VPPEnumType, VPPUnionType
-from . vpp_serializer import VPPMessage, vpp_get_type, VPPTypeAlias
+from .vpp_format import verify_enum_hint
+from .vpp_serializer import VPPType, VPPEnumType, VPPEnumFlagType, VPPUnionType
+from .vpp_serializer import VPPMessage, vpp_get_type, VPPTypeAlias
 
 try:
     import VppTransport
 except ModuleNotFoundError:
 
 try:
     import VppTransport
 except ModuleNotFoundError:
+
     class V:
         """placeholder for VppTransport as the implementation is dependent on
         VPPAPIClient's initialization values
     class V:
         """placeholder for VppTransport as the implementation is dependent on
         VPPAPIClient's initialization values
@@ -44,13 +45,22 @@ except ModuleNotFoundError:
 
     VppTransport = V
 
 
     VppTransport = V
 
-logger = logging.getLogger('vpp_papi')
+from .vpp_transport_socket import VppTransport
+
+logger = logging.getLogger("vpp_papi")
 logger.addHandler(logging.NullHandler())
 
 logger.addHandler(logging.NullHandler())
 
-__all__ = ('FuncWrapper', 'VPP', 'VppApiDynamicMethodHolder',
-           'VppEnum', 'VppEnumType',
-           'VPPIOError', 'VPPRuntimeError', 'VPPValueError',
-           'VPPApiClient', )
+__all__ = (
+    "FuncWrapper",
+    "VppApiDynamicMethodHolder",
+    "VppEnum",
+    "VppEnumType",
+    "VppEnumFlag",
+    "VPPIOError",
+    "VPPRuntimeError",
+    "VPPValueError",
+    "VPPApiClient",
+)
 
 
 def metaclass(metaclass):
 
 
 def metaclass(metaclass):
@@ -72,16 +82,19 @@ class VppEnum:
     pass
 
 
     pass
 
 
+@metaclass(VppEnumType)
+class VppEnumFlag:
+    pass
+
+
 def vpp_atexit(vpp_weakref):
     """Clean up VPP connection on shutdown."""
     vpp_instance = vpp_weakref()
     if vpp_instance and vpp_instance.transport.connected:
 def vpp_atexit(vpp_weakref):
     """Clean up VPP connection on shutdown."""
     vpp_instance = vpp_weakref()
     if vpp_instance and vpp_instance.transport.connected:
-        logger.debug('Cleaning up VPP on exit')
+        logger.debug("Cleaning up VPP on exit")
         vpp_instance.disconnect()
 
 
         vpp_instance.disconnect()
 
 
-
-
 def add_convenience_methods():
     # provide convenience methods to IP[46]Address.vapi_af
     def _vapi_af(self):
 def add_convenience_methods():
     # provide convenience methods to IP[46]Address.vapi_af
     def _vapi_af(self):
@@ -93,9 +106,9 @@ def add_convenience_methods():
 
     def _vapi_af_name(self):
         if 6 == self._version:
 
     def _vapi_af_name(self):
         if 6 == self._version:
-            return 'ip6'
+            return "ip6"
         if 4 == self._version:
         if 4 == self._version:
-            return 'ip4'
+            return "ip4"
         raise ValueError("Invalid _version.")
 
     ipaddress._IPAddressBase.vapi_af = property(_vapi_af)
         raise ValueError("Invalid _version.")
 
     ipaddress._IPAddressBase.vapi_af = property(_vapi_af)
@@ -116,7 +129,7 @@ class FuncWrapper:
         return self._func(**kwargs)
 
     def __repr__(self):
         return self._func(**kwargs)
 
     def __repr__(self):
-        return '<FuncWrapper(func=<%s(%s)>)>' % (self.__name__, self.__doc__)
+        return "<FuncWrapper(func=<%s(%s)>)>" % (self.__name__, self.__doc__)
 
 
 class VPPApiError(Exception):
 
 
 class VPPApiError(Exception):
@@ -156,7 +169,8 @@ class VPPApiJSONFiles:
         # perhaps we're in the 'src/scripts' or 'src/vpp-api/python' dir;
         # in which case, plot a course to likely places in the src tree
         import __main__ as main
         # perhaps we're in the 'src/scripts' or 'src/vpp-api/python' dir;
         # in which case, plot a course to likely places in the src tree
         import __main__ as main
-        if hasattr(main, '__file__'):
+
+        if hasattr(main, "__file__"):
             # get the path of the calling script
             localdir = os.path.dirname(os.path.realpath(main.__file__))
         else:
             # get the path of the calling script
             localdir = os.path.dirname(os.path.realpath(main.__file__))
         else:
@@ -166,7 +180,7 @@ class VPPApiJSONFiles:
 
         def dmatch(dir):
             """Match dir against right-hand components of the script dir"""
 
         def dmatch(dir):
             """Match dir against right-hand components of the script dir"""
-            d = dir.split('/')  # param 'dir' assumes a / separator
+            d = dir.split("/")  # param 'dir' assumes a / separator
             length = len(d)
             return len(localdir_s) > length and localdir_s[-length:] == d
 
             length = len(d)
             return len(localdir_s) > length and localdir_s[-length:] == d
 
@@ -175,43 +189,45 @@ class VPPApiJSONFiles:
             'variant'  (typically '' or '_debug')"""
             # Since 'core' and 'plugin' files are staged
             # in separate directories, we target the parent dir.
             'variant'  (typically '' or '_debug')"""
             # Since 'core' and 'plugin' files are staged
             # in separate directories, we target the parent dir.
-            return os.path.sep.join((
-                srcdir,
-                'build-root',
-                'install-vpp%s-native' % variant,
-                'vpp',
-                'share',
-                'vpp',
-                'api',
-            ))
+            return os.path.sep.join(
+                (
+                    srcdir,
+                    "build-root",
+                    "install-vpp%s-native" % variant,
+                    "vpp",
+                    "share",
+                    "vpp",
+                    "api",
+                )
+            )
 
         srcdir = None
 
         srcdir = None
-        if dmatch('src/scripts'):
+        if dmatch("src/scripts"):
             srcdir = os.path.sep.join(localdir_s[:-2])
             srcdir = os.path.sep.join(localdir_s[:-2])
-        elif dmatch('src/vpp-api/python'):
+        elif dmatch("src/vpp-api/python"):
             srcdir = os.path.sep.join(localdir_s[:-3])
             srcdir = os.path.sep.join(localdir_s[:-3])
-        elif dmatch('test'):
+        elif dmatch("test"):
             # we're apparently running tests
             srcdir = os.path.sep.join(localdir_s[:-1])
 
         if srcdir:
             # we're in the source tree, try both the debug and release
             # variants.
             # we're apparently running tests
             srcdir = os.path.sep.join(localdir_s[:-1])
 
         if srcdir:
             # we're in the source tree, try both the debug and release
             # variants.
-            dirs.append(sdir(srcdir, '_debug'))
-            dirs.append(sdir(srcdir, ''))
+            dirs.append(sdir(srcdir, "_debug"))
+            dirs.append(sdir(srcdir, ""))
 
         # Test for staged copies of the scripts
         # For these, since we explicitly know if we're running a debug versus
         # release variant, target only the relevant directory
 
         # Test for staged copies of the scripts
         # For these, since we explicitly know if we're running a debug versus
         # release variant, target only the relevant directory
-        if dmatch('build-root/install-vpp_debug-native/vpp/bin'):
+        if dmatch("build-root/install-vpp_debug-native/vpp/bin"):
             srcdir = os.path.sep.join(localdir_s[:-4])
             srcdir = os.path.sep.join(localdir_s[:-4])
-            dirs.append(sdir(srcdir, '_debug'))
-        if dmatch('build-root/install-vpp-native/vpp/bin'):
+            dirs.append(sdir(srcdir, "_debug"))
+        if dmatch("build-root/install-vpp-native/vpp/bin"):
             srcdir = os.path.sep.join(localdir_s[:-4])
             srcdir = os.path.sep.join(localdir_s[:-4])
-            dirs.append(sdir(srcdir, ''))
+            dirs.append(sdir(srcdir, ""))
 
         # finally, try the location system packages typically install into
 
         # finally, try the location system packages typically install into
-        dirs.append(os.path.sep.join(('', 'usr', 'share', 'vpp', 'api')))
+        dirs.append(os.path.sep.join(("", "usr", "share", "vpp", "api")))
 
         # check the directories for existence; first one wins
         for dir in dirs:
 
         # check the directories for existence; first one wins
         for dir in dirs:
@@ -221,7 +237,7 @@ class VPPApiJSONFiles:
         return None
 
     @classmethod
         return None
 
     @classmethod
-    def find_api_files(cls, api_dir=None, patterns='*'):  # -> list
+    def find_api_files(cls, api_dir=None, patterns="*"):  # -> list
         """Find API definition files from the given directory tree with the
         given pattern. If no directory is given then find_api_dir() is used
         to locate one. If no pattern is given then all definition files found
         """Find API definition files from the given directory tree with the
         given pattern. If no directory is given then find_api_dir() is used
         to locate one. If no pattern is given then all definition files found
@@ -247,9 +263,9 @@ class VPPApiJSONFiles:
                 raise VPPApiError("api_dir cannot be located")
 
         if isinstance(patterns, list) or isinstance(patterns, tuple):
                 raise VPPApiError("api_dir cannot be located")
 
         if isinstance(patterns, list) or isinstance(patterns, tuple):
-            patterns = [p.strip() + '.api.json' for p in patterns]
+            patterns = [p.strip() + ".api.json" for p in patterns]
         else:
         else:
-            patterns = [p.strip() + '.api.json' for p in patterns.split(",")]
+            patterns = [p.strip() + ".api.json" for p in patterns.split(",")]
 
         api_files = []
         for root, dirnames, files in os.walk(api_dir):
 
         api_files = []
         for root, dirnames, files in os.walk(api_dir):
@@ -276,34 +292,39 @@ class VPPApiJSONFiles:
         services = {}
         messages = {}
         try:
         services = {}
         messages = {}
         try:
-            for t in api['enums']:
-                t[0] = 'vl_api_' + t[0] + '_t'
-                types[t[0]] = {'type': 'enum', 'data': t}
+            for t in api["enums"]:
+                t[0] = "vl_api_" + t[0] + "_t"
+                types[t[0]] = {"type": "enum", "data": t}
         except KeyError:
             pass
         except KeyError:
             pass
-
         try:
         try:
-            for t in api['unions']:
-                t[0] = 'vl_api_' + t[0] + '_t'
-                types[t[0]] = {'type': 'union', 'data': t}
+            for t in api["enumflags"]:
+                t[0] = "vl_api_" + t[0] + "_t"
+                types[t[0]] = {"type": "enum", "data": t}
+        except KeyError:
+            pass
+        try:
+            for t in api["unions"]:
+                t[0] = "vl_api_" + t[0] + "_t"
+                types[t[0]] = {"type": "union", "data": t}
         except KeyError:
             pass
 
         try:
         except KeyError:
             pass
 
         try:
-            for t in api['types']:
-                t[0] = 'vl_api_' + t[0] + '_t'
-                types[t[0]] = {'type': 'type', 'data': t}
+            for t in api["types"]:
+                t[0] = "vl_api_" + t[0] + "_t"
+                types[t[0]] = {"type": "type", "data": t}
         except KeyError:
             pass
 
         try:
         except KeyError:
             pass
 
         try:
-            for t, v in api['aliases'].items():
-                types['vl_api_' + t + '_t'] = {'type': 'alias', 'data': v}
+            for t, v in api["aliases"].items():
+                types["vl_api_" + t + "_t"] = {"type": "alias", "data": v}
         except KeyError:
             pass
 
         try:
         except KeyError:
             pass
 
         try:
-            services.update(api['services'])
+            services.update(api["services"])
         except KeyError:
             pass
 
         except KeyError:
             pass
 
@@ -311,24 +332,30 @@ class VPPApiJSONFiles:
         while True:
             unresolved = {}
             for k, v in types.items():
         while True:
             unresolved = {}
             for k, v in types.items():
-                t = v['data']
+                t = v["data"]
                 if not vpp_get_type(k):
                 if not vpp_get_type(k):
-                    if v['type'] == 'enum':
+                    if v["type"] == "enum":
                         try:
                             VPPEnumType(t[0], t[1:])
                         except ValueError:
                             unresolved[k] = v
                         try:
                             VPPEnumType(t[0], t[1:])
                         except ValueError:
                             unresolved[k] = v
-                    elif v['type'] == 'union':
+                if not vpp_get_type(k):
+                    if v["type"] == "enumflag":
+                        try:
+                            VPPEnumFlagType(t[0], t[1:])
+                        except ValueError:
+                            unresolved[k] = v
+                    elif v["type"] == "union":
                         try:
                             VPPUnionType(t[0], t[1:])
                         except ValueError:
                             unresolved[k] = v
                         try:
                             VPPUnionType(t[0], t[1:])
                         except ValueError:
                             unresolved[k] = v
-                    elif v['type'] == 'type':
+                    elif v["type"] == "type":
                         try:
                             VPPType(t[0], t[1:])
                         except ValueError:
                             unresolved[k] = v
                         try:
                             VPPType(t[0], t[1:])
                         except ValueError:
                             unresolved[k] = v
-                    elif v['type'] == 'alias':
+                    elif v["type"] == "alias":
                         try:
                             VPPTypeAlias(k, t)
                         except ValueError:
                         try:
                             VPPTypeAlias(k, t)
                         except ValueError:
@@ -336,17 +363,16 @@ class VPPApiJSONFiles:
             if len(unresolved) == 0:
                 break
             if i > 3:
             if len(unresolved) == 0:
                 break
             if i > 3:
-                raise VPPValueError('Unresolved type definitions {}'
-                                    .format(unresolved))
+                raise VPPValueError("Unresolved type definitions {}".format(unresolved))
             types = unresolved
             i += 1
         try:
             types = unresolved
             i += 1
         try:
-            for m in api['messages']:
+            for m in api["messages"]:
                 try:
                     messages[m[0]] = VPPMessage(m[0], m[1:])
                 except VPPNotImplementedError:
                     ### OLE FIXME
                 try:
                     messages[m[0]] = VPPMessage(m[0], m[1:])
                 except VPPNotImplementedError:
                     ### OLE FIXME
-                    logger.error('Not implemented error for {}'.format(m[0]))
+                    logger.error("Not implemented error for {}".format(m[0]))
         except KeyError:
             pass
         return messages, services
         except KeyError:
             pass
         return messages, services
@@ -364,6 +390,7 @@ class VPPApiClient:
     provides a means to register a callback function to receive
     these messages in a background thread.
     """
     provides a means to register a callback function to receive
     these messages in a background thread.
     """
+
     apidir = None
     VPPApiError = VPPApiError
     VPPRuntimeError = VPPRuntimeError
     apidir = None
     VPPApiError = VPPApiError
     VPPRuntimeError = VPPRuntimeError
@@ -371,11 +398,18 @@ class VPPApiClient:
     VPPNotImplementedError = VPPNotImplementedError
     VPPIOError = VPPIOError
 
     VPPNotImplementedError = VPPNotImplementedError
     VPPIOError = VPPIOError
 
-
-    def __init__(self, apifiles=None, testmode=False, async_thread=True,
-                 logger=None, loglevel=None,
-                 read_timeout=5, use_socket=False,
-                 server_address='/run/vpp/api.sock'):
+    def __init__(
+        self,
+        *,
+        apifiles=None,
+        testmode=False,
+        async_thread=True,
+        logger=None,
+        loglevel=None,
+        read_timeout=5,
+        use_socket=True,
+        server_address="/run/vpp/api.sock",
+    ):
         """Create a VPP API object.
 
         apifiles is a list of files containing API
         """Create a VPP API object.
 
         apifiles is a list of files containing API
@@ -390,7 +424,8 @@ class VPPApiClient:
         """
         if logger is None:
             logger = logging.getLogger(
         """
         if logger is None:
             logger = logging.getLogger(
-                "{}.{}".format(__name__, self.__class__.__name__))
+                "{}.{}".format(__name__, self.__class__.__name__)
+            )
             if loglevel is not None:
                 logger.setLevel(loglevel)
         self.logger = logger
             if loglevel is not None:
                 logger.setLevel(loglevel)
         self.logger = logger
@@ -399,8 +434,7 @@ class VPPApiClient:
         self.services = {}
         self.id_names = []
         self.id_msgdef = []
         self.services = {}
         self.id_names = []
         self.id_msgdef = []
-        self.header = VPPType('header', [['u16', 'msgid'],
-                                         ['u32', 'client_index']])
+        self.header = VPPType("header", [["u16", "msgid"], ["u32", "client_index"]])
         self.apifiles = []
         self.event_callback = None
         self.message_queue = queue.Queue()
         self.apifiles = []
         self.event_callback = None
         self.message_queue = queue.Queue()
@@ -408,20 +442,19 @@ class VPPApiClient:
         self.async_thread = async_thread
         self.event_thread = None
         self.testmode = testmode
         self.async_thread = async_thread
         self.event_thread = None
         self.testmode = testmode
-        self.use_socket = use_socket
         self.server_address = server_address
         self._apifiles = apifiles
         self.stats = {}
 
         self.server_address = server_address
         self._apifiles = apifiles
         self.stats = {}
 
-        if use_socket:
-            from . vpp_transport_socket import VppTransport
-        else:
-            from . vpp_transport_shmem import VppTransport
-
         if not apifiles:
             # Pick up API definitions from default directory
             try:
         if not apifiles:
             # Pick up API definitions from default directory
             try:
-                apifiles = VPPApiJSONFiles.find_api_files(self.apidir)
+                if isinstance(self.apidir, list):
+                    apifiles = []
+                    for d in self.apidir:
+                        apifiles += VPPApiJSONFiles.find_api_files(d)
+                else:
+                    apifiles = VPPApiJSONFiles.find_api_files(self.apidir)
             except (RuntimeError, VPPApiError):
                 # In test mode we don't care that we can't find the API files
                 if testmode:
             except (RuntimeError, VPPApiError):
                 # In test mode we don't care that we can't find the API files
                 if testmode:
@@ -439,13 +472,13 @@ class VPPApiClient:
 
         # Basic sanity check
         if len(self.messages) == 0 and not testmode:
 
         # Basic sanity check
         if len(self.messages) == 0 and not testmode:
-            raise VPPValueError(1, 'Missing JSON message definitions')
-        if not(verify_enum_hint(VppEnum.vl_api_address_family_t)):
-            raise VPPRuntimeError("Invalid address family hints. "
-                                  "Cannot continue.")
+            raise VPPValueError(1, "Missing JSON message definitions")
+        if not (verify_enum_hint(VppEnum.vl_api_address_family_t)):
+            raise VPPRuntimeError("Invalid address family hints. " "Cannot continue.")
 
 
-        self.transport = VppTransport(self, read_timeout=read_timeout,
-                                      server_address=server_address)
+        self.transport = VppTransport(
+            self, read_timeout=read_timeout, server_address=server_address
+        )
         # Make sure we allow VPP to clean up the message rings.
         atexit.register(vpp_atexit, weakref.ref(self))
 
         # Make sure we allow VPP to clean up the message rings.
         atexit.register(vpp_atexit, weakref.ref(self))
 
@@ -456,6 +489,7 @@ class VPPApiClient:
 
     class ContextId:
         """Multiprocessing-safe provider of unique context IDs."""
 
     class ContextId:
         """Multiprocessing-safe provider of unique context IDs."""
+
         def __init__(self):
             self.context = mp.Value(ctypes.c_uint, 0)
             self.lock = mp.Lock()
         def __init__(self):
             self.context = mp.Value(ctypes.c_uint, 0)
             self.lock = mp.Lock()
@@ -465,6 +499,7 @@ class VPPApiClient:
             with self.lock:
                 self.context.value += 1
                 return self.context.value
             with self.lock:
                 self.context.value += 1
                 return self.context.value
+
     get_context = ContextId()
 
     def get_type(self, name):
     get_context = ContextId()
 
     def get_type(self, name):
@@ -477,27 +512,37 @@ class VPPApiClient:
         return self._api
 
     def make_function(self, msg, i, multipart, do_async):
         return self._api
 
     def make_function(self, msg, i, multipart, do_async):
-        if (do_async):
+        if do_async:
+
             def f(**kwargs):
                 return self._call_vpp_async(i, msg, **kwargs)
             def f(**kwargs):
                 return self._call_vpp_async(i, msg, **kwargs)
+
         else:
         else:
+
             def f(**kwargs):
                 return self._call_vpp(i, msg, multipart, **kwargs)
 
         f.__name__ = str(msg.name)
             def f(**kwargs):
                 return self._call_vpp(i, msg, multipart, **kwargs)
 
         f.__name__ = str(msg.name)
-        f.__doc__ = ", ".join(["%s %s" %
-                               (msg.fieldtypes[j], k)
-                               for j, k in enumerate(msg.fields)])
+        f.__doc__ = ", ".join(
+            ["%s %s" % (msg.fieldtypes[j], k) for j, k in enumerate(msg.fields)]
+        )
         f.msg = msg
 
         return f
 
         f.msg = msg
 
         return f
 
+    def make_pack_function(self, msg, i, multipart):
+        def f(**kwargs):
+            return self._call_vpp_pack(i, msg, **kwargs)
+
+        f.msg = msg
+        return f
+
     def _register_functions(self, do_async=False):
         self.id_names = [None] * (self.vpp_dictionary_maxid + 1)
         self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1)
         self._api = VppApiDynamicMethodHolder()
         for name, msg in self.messages.items():
     def _register_functions(self, do_async=False):
         self.id_names = [None] * (self.vpp_dictionary_maxid + 1)
         self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1)
         self._api = VppApiDynamicMethodHolder()
         for name, msg in self.messages.items():
-            n = name + '_' + msg.crc[2:]
+            n = name + "_" + msg.crc[2:]
             i = self.transport.get_msg_index(n)
             if i > 0:
                 self.id_msgdef[i] = msg
             i = self.transport.get_msg_index(n)
             if i > 0:
                 self.id_msgdef[i] = msg
@@ -506,30 +551,29 @@ class VPPApiClient:
                 # Create function for client side messages.
                 if name in self.services:
                     f = self.make_function(msg, i, self.services[name], do_async)
                 # Create function for client side messages.
                 if name in self.services:
                     f = self.make_function(msg, i, self.services[name], do_async)
+                    f_pack = self.make_pack_function(msg, i, self.services[name])
                     setattr(self._api, name, FuncWrapper(f))
                     setattr(self._api, name, FuncWrapper(f))
+                    setattr(self._api, name + "_pack", FuncWrapper(f_pack))
             else:
             else:
-                self.logger.debug(
-                    'No such message type or failed CRC checksum: %s', n)
+                self.logger.debug("No such message type or failed CRC checksum: %s", n)
 
 
-    def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen,
-                         do_async):
-        pfx = chroot_prefix.encode('utf-8') if chroot_prefix else None
+    def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen, do_async):
+        pfx = chroot_prefix.encode("utf-8") if chroot_prefix else None
 
 
-        rv = self.transport.connect(name, pfx,
-                                    msg_handler, rx_qlen)
+        rv = self.transport.connect(name, pfx, msg_handler, rx_qlen, do_async)
         if rv != 0:
         if rv != 0:
-            raise VPPIOError(2, 'Connect failed')
+            raise VPPIOError(2, "Connect failed")
         self.vpp_dictionary_maxid = self.transport.msg_table_max_index()
         self._register_functions(do_async=do_async)
 
         # Initialise control ping
         self.vpp_dictionary_maxid = self.transport.msg_table_max_index()
         self._register_functions(do_async=do_async)
 
         # Initialise control ping
-        crc = self.messages['control_ping'].crc
+        crc = self.messages["control_ping"].crc
         self.control_ping_index = self.transport.get_msg_index(
         self.control_ping_index = self.transport.get_msg_index(
-            ('control_ping' + '_' + crc[2:]))
-        self.control_ping_msgdef = self.messages['control_ping']
+            ("control_ping" + "_" + crc[2:])
+        )
+        self.control_ping_msgdef = self.messages["control_ping"]
         if self.async_thread:
         if self.async_thread:
-            self.event_thread = threading.Thread(
-                target=self.thread_msg_handler)
+            self.event_thread = threading.Thread(target=self.thread_msg_handler)
             self.event_thread.daemon = True
             self.event_thread.start()
         else:
             self.event_thread.daemon = True
             self.event_thread.start()
         else:
@@ -546,8 +590,9 @@ class VPPApiClient:
         client and server.
         """
         msg_handler = self.transport.get_callback(do_async)
         client and server.
         """
         msg_handler = self.transport.get_callback(do_async)
-        return self.connect_internal(name, msg_handler, chroot_prefix, rx_qlen,
-                                     do_async)
+        return self.connect_internal(
+            name, msg_handler, chroot_prefix, rx_qlen, do_async
+        )
 
     def connect_sync(self, name, chroot_prefix=None, rx_qlen=32):
         """Attach to VPP in synchronous mode. Application must poll for events.
 
     def connect_sync(self, name, chroot_prefix=None, rx_qlen=32):
         """Attach to VPP in synchronous mode. Application must poll for events.
@@ -558,8 +603,7 @@ class VPPApiClient:
         client and server.
         """
 
         client and server.
         """
 
-        return self.connect_internal(name, None, chroot_prefix, rx_qlen,
-                                     do_async=False)
+        return self.connect_internal(name, None, chroot_prefix, rx_qlen, do_async=False)
 
     def disconnect(self):
         """Detach from VPP."""
 
     def disconnect(self):
         """Detach from VPP."""
@@ -580,42 +624,43 @@ class VPPApiClient:
         # If we have a context, then use the context to find any
         # request waiting for a reply
         context = 0
         # If we have a context, then use the context to find any
         # request waiting for a reply
         context = 0
-        if hasattr(r, 'context') and r.context > 0:
+        if hasattr(r, "context") and r.context > 0:
             context = r.context
 
         if context == 0:
             # No context -> async notification that we feed to the callback
             self.message_queue.put_nowait(r)
         else:
             context = r.context
 
         if context == 0:
             # No context -> async notification that we feed to the callback
             self.message_queue.put_nowait(r)
         else:
-            raise VPPIOError(2, 'RPC reply message received in event handler')
+            raise VPPIOError(2, "RPC reply message received in event handler")
 
     def has_context(self, msg):
         if len(msg) < 10:
             return False
 
 
     def has_context(self, msg):
         if len(msg) < 10:
             return False
 
-        header = VPPType('header_with_context', [['u16', 'msgid'],
-                                                 ['u32', 'client_index'],
-                                                 ['u32', 'context']])
+        header = VPPType(
+            "header_with_context",
+            [["u16", "msgid"], ["u32", "client_index"], ["u32", "context"]],
+        )
 
         (i, ci, context), size = header.unpack(msg, 0)
 
         (i, ci, context), size = header.unpack(msg, 0)
-        if self.id_names[i] == 'rx_thread_exit':
+        if self.id_names[i] == "rx_thread_exit":
             return
 
         #
         # Decode message and returns a tuple.
         #
         msgobj = self.id_msgdef[i]
             return
 
         #
         # Decode message and returns a tuple.
         #
         msgobj = self.id_msgdef[i]
-        if 'context' in msgobj.field_by_name and context >= 0:
+        if "context" in msgobj.field_by_name and context >= 0:
             return True
         return False
 
     def decode_incoming_msg(self, msg, no_type_conversion=False):
         if not msg:
             return True
         return False
 
     def decode_incoming_msg(self, msg, no_type_conversion=False):
         if not msg:
-            logger.warning('vpp_api.read failed')
+            logger.warning("vpp_api.read failed")
             return
 
         (i, ci), size = self.header.unpack(msg, 0)
             return
 
         (i, ci), size = self.header.unpack(msg, 0)
-        if self.id_names[i] == 'rx_thread_exit':
+        if self.id_names[i] == "rx_thread_exit":
             return
 
         #
             return
 
         #
@@ -623,7 +668,7 @@ class VPPApiClient:
         #
         msgobj = self.id_msgdef[i]
         if not msgobj:
         #
         msgobj = self.id_msgdef[i]
         if not msgobj:
-            raise VPPIOError(2, 'Reply message undefined')
+            raise VPPIOError(2, "Reply message undefined")
 
         r, size = msgobj.unpack(msg, ntc=no_type_conversion)
         return r
 
         r, size = msgobj.unpack(msg, ntc=no_type_conversion)
         return r
@@ -644,34 +689,41 @@ class VPPApiClient:
 
     def _control_ping(self, context):
         """Send a ping command."""
 
     def _control_ping(self, context):
         """Send a ping command."""
-        self._call_vpp_async(self.control_ping_index,
-                             self.control_ping_msgdef,
-                             context=context)
+        self._call_vpp_async(
+            self.control_ping_index, self.control_ping_msgdef, context=context
+        )
 
     def validate_args(self, msg, kwargs):
         d = set(kwargs.keys()) - set(msg.field_by_name.keys())
         if d:
 
     def validate_args(self, msg, kwargs):
         d = set(kwargs.keys()) - set(msg.field_by_name.keys())
         if d:
-            raise VPPValueError('Invalid argument {} to {}'
-                                .format(list(d), msg.name))
+            raise VPPValueError("Invalid argument {} to {}".format(list(d), msg.name))
 
     def _add_stat(self, name, ms):
         if not name in self.stats:
 
     def _add_stat(self, name, ms):
         if not name in self.stats:
-            self.stats[name] = {'max': ms, 'count': 1, 'avg': ms}
+            self.stats[name] = {"max": ms, "count": 1, "avg": ms}
         else:
         else:
-            if ms > self.stats[name]['max']:
-                self.stats[name]['max'] = ms
-            self.stats[name]['count'] += 1
-            n = self.stats[name]['count']
-            self.stats[name]['avg'] = self.stats[name]['avg'] * (n - 1) / n + ms / n
+            if ms > self.stats[name]["max"]:
+                self.stats[name]["max"] = ms
+            self.stats[name]["count"] += 1
+            n = self.stats[name]["count"]
+            self.stats[name]["avg"] = self.stats[name]["avg"] * (n - 1) / n + ms / n
 
     def get_stats(self):
 
     def get_stats(self):
-        s = '\n=== API PAPI STATISTICS ===\n'
-        s += '{:<30} {:>4} {:>6} {:>6}\n'.format('message', 'cnt', 'avg', 'max')
-        for n in sorted(self.stats.items(), key=lambda v: v[1]['avg'], reverse=True):
-            s += '{:<30} {:>4} {:>6.2f} {:>6.2f}\n'.format(n[0], n[1]['count'],
-                                                           n[1]['avg'], n[1]['max'])
+        s = "\n=== API PAPI STATISTICS ===\n"
+        s += "{:<30} {:>4} {:>6} {:>6}\n".format("message", "cnt", "avg", "max")
+        for n in sorted(self.stats.items(), key=lambda v: v[1]["avg"], reverse=True):
+            s += "{:<30} {:>4} {:>6.2f} {:>6.2f}\n".format(
+                n[0], n[1]["count"], n[1]["avg"], n[1]["max"]
+            )
         return s
 
         return s
 
+    def get_field_options(self, msg, fld_name):
+        # when there is an option, the msgdef has 3 elements.
+        # ['u32', 'ring_size', {'default': 1024}]
+        for _def in self.messages[msg].msgdef:
+            if isinstance(_def, list) and len(_def) == 3 and _def[1] == fld_name:
+                return _def[2]
+
     def _call_vpp(self, i, msgdef, service, **kwargs):
         """Given a message, send the message and await a reply.
 
     def _call_vpp(self, i, msgdef, service, **kwargs):
         """Given a message, send the message and await a reply.
 
@@ -688,25 +740,26 @@ class VPPApiClient:
         no response within the timeout window.
         """
         ts = time.time()
         no response within the timeout window.
         """
         ts = time.time()
-        if 'context' not in kwargs:
+        if "context" not in kwargs:
             context = self.get_context()
             context = self.get_context()
-            kwargs['context'] = context
+            kwargs["context"] = context
         else:
         else:
-            context = kwargs['context']
-        kwargs['_vl_msg_id'] = i
+            context = kwargs["context"]
+        kwargs["_vl_msg_id"] = i
 
 
-        no_type_conversion = kwargs.pop('_no_type_conversion', False)
-        timeout = kwargs.pop('_timeout', None)
+        no_type_conversion = kwargs.pop("_no_type_conversion", False)
+        timeout = kwargs.pop("_timeout", None)
 
         try:
             if self.transport.socket_index:
 
         try:
             if self.transport.socket_index:
-                kwargs['client_index'] = self.transport.socket_index
+                kwargs["client_index"] = self.transport.socket_index
         except AttributeError:
             pass
         self.validate_args(msgdef, kwargs)
 
         except AttributeError:
             pass
         self.validate_args(msgdef, kwargs)
 
-        s = 'Calling {}({})'.format(msgdef.name,
-            ','.join(['{!r}:{!r}'.format(k, v) for k, v in kwargs.items()]))
+        s = "Calling {}({})".format(
+            msgdef.name, ",".join(["{!r}:{!r}".format(k, v) for k, v in kwargs.items()])
+        )
         self.logger.debug(s)
 
         b = msgdef.pack(kwargs)
         self.logger.debug(s)
 
         b = msgdef.pack(kwargs)
@@ -714,17 +767,17 @@ class VPPApiClient:
 
         self.transport.write(b)
 
 
         self.transport.write(b)
 
-        msgreply = service['reply']
-        stream = True if 'stream' in service else False
+        msgreply = service["reply"]
+        stream = True if "stream" in service else False
         if stream:
         if stream:
-            if 'stream_msg' in service:
+            if "stream_msg" in service:
                 # New service['reply'] = _reply and service['stream_message'] = _details
                 # New service['reply'] = _reply and service['stream_message'] = _details
-                stream_message = service['stream_msg']
-                modern =True
+                stream_message = service["stream_msg"]
+                modern = True
             else:
                 # Old  service['reply'] = _details
                 stream_message = msgreply
             else:
                 # Old  service['reply'] = _details
                 stream_message = msgreply
-                msgreply = 'control_ping_reply'
+                msgreply = "control_ping_reply"
                 modern = False
                 # Send a ping after the request - we use its response
                 # to detect that we have seen all results.
                 modern = False
                 # Send a ping after the request - we use its response
                 # to detect that we have seen all results.
@@ -732,22 +785,22 @@ class VPPApiClient:
 
         # Block until we get a reply.
         rl = []
 
         # Block until we get a reply.
         rl = []
-        while (True):
+        while True:
             r = self.read_blocking(no_type_conversion, timeout)
             if r is None:
             r = self.read_blocking(no_type_conversion, timeout)
             if r is None:
-                raise VPPIOError(2, 'VPP API client: read failed')
+                raise VPPIOError(2, "VPP API client: read failed")
             msgname = type(r).__name__
             if context not in r or r.context == 0 or context != r.context:
                 # Message being queued
                 self.message_queue.put_nowait(r)
                 continue
             if msgname != msgreply and (stream and (msgname != stream_message)):
             msgname = type(r).__name__
             if context not in r or r.context == 0 or context != r.context:
                 # Message being queued
                 self.message_queue.put_nowait(r)
                 continue
             if msgname != msgreply and (stream and (msgname != stream_message)):
-                print('REPLY MISMATCH', msgreply, msgname, stream_message, stream)
+                print("REPLY MISMATCH", msgreply, msgname, stream_message, stream)
             if not stream:
                 rl = r
                 break
             if msgname == msgreply:
             if not stream:
                 rl = r
                 break
             if msgname == msgreply:
-                if modern: # Return both reply and list
+                if modern:  # Return both reply and list
                     rl = r, rl
                 break
 
                     rl = r, rl
                 break
 
@@ -755,7 +808,7 @@ class VPPApiClient:
 
         self.transport.resume()
 
 
         self.transport.resume()
 
-        s = 'Return value: {!r}'.format(r)
+        s = "Return value: {!r}".format(r)
         if len(s) > 80:
             s = s[:80] + "..."
         self.logger.debug(s)
         if len(s) > 80:
             s = s[:80] + "..."
         self.logger.debug(s)
@@ -776,22 +829,29 @@ class VPPApiClient:
         The returned context will help with assigning which call
         the reply belongs to.
         """
         The returned context will help with assigning which call
         the reply belongs to.
         """
-        if 'context' not in kwargs:
+        if "context" not in kwargs:
             context = self.get_context()
             context = self.get_context()
-            kwargs['context'] = context
+            kwargs["context"] = context
         else:
         else:
-            context = kwargs['context']
+            context = kwargs["context"]
         try:
             if self.transport.socket_index:
         try:
             if self.transport.socket_index:
-                kwargs['client_index'] = self.transport.socket_index
+                kwargs["client_index"] = self.transport.socket_index
         except AttributeError:
         except AttributeError:
-            kwargs['client_index'] = 0
-        kwargs['_vl_msg_id'] = i
+            kwargs["client_index"] = 0
+        kwargs["_vl_msg_id"] = i
         b = msg.pack(kwargs)
 
         self.transport.write(b)
         return context
 
         b = msg.pack(kwargs)
 
         self.transport.write(b)
         return context
 
+    def _call_vpp_pack(self, i, msg, **kwargs):
+        """Given a message, return the binary representation."""
+        kwargs["_vl_msg_id"] = i
+        kwargs["client_index"] = 0
+        kwargs["context"] = 0
+        return msg.pack(kwargs)
+
     def read_blocking(self, no_type_conversion=False, timeout=None):
         """Get next received message from transport within timeout, decoded.
 
     def read_blocking(self, no_type_conversion=False, timeout=None):
         """Get next received message from transport within timeout, decoded.
 
@@ -872,38 +932,37 @@ class VPPApiClient:
         """Return VPPs API message table as name_crc dictionary,
         filtered by message name list."""
 
         """Return VPPs API message table as name_crc dictionary,
         filtered by message name list."""
 
-        replies = [self.services[n]['reply'] for n in msglist]
+        replies = [self.services[n]["reply"] for n in msglist]
         message_table_filtered = {}
         for name in msglist + replies:
         message_table_filtered = {}
         for name in msglist + replies:
-            for k,v in self.transport.message_table.items():
+            for k, v in self.transport.message_table.items():
                 if k.startswith(name):
                     message_table_filtered[k] = v
                     break
         return message_table_filtered
 
     def __repr__(self):
                 if k.startswith(name):
                     message_table_filtered[k] = v
                     break
         return message_table_filtered
 
     def __repr__(self):
-        return "<VPPApiClient apifiles=%s, testmode=%s, async_thread=%s, " \
-               "logger=%s, read_timeout=%s, use_socket=%s, " \
-               "server_address='%s'>" % (
-                   self._apifiles, self.testmode, self.async_thread,
-                   self.logger, self.read_timeout, self.use_socket,
-                   self.server_address)
+        return (
+            "<VPPApiClient apifiles=%s, testmode=%s, async_thread=%s, "
+            "logger=%s, read_timeout=%s, "
+            "server_address='%s'>"
+            % (
+                self._apifiles,
+                self.testmode,
+                self.async_thread,
+                self.logger,
+                self.read_timeout,
+                self.server_address,
+            )
+        )
 
     def details_iter(self, f, **kwargs):
         cursor = 0
         while True:
 
     def details_iter(self, f, **kwargs):
         cursor = 0
         while True:
-            kwargs['cursor'] = cursor
+            kwargs["cursor"] = cursor
             rv, details = f(**kwargs)
             rv, details = f(**kwargs)
-            #
-            # Convert to yield from details when we only support python 3
-            #
             for d in details:
                 yield d
             if rv.retval == 0 or rv.retval != -165:
                 break
             cursor = rv.cursor
             for d in details:
                 yield d
             if rv.retval == 0 or rv.retval != -165:
                 break
             cursor = rv.cursor
-
-# Provide the old name for backward compatibility.
-VPP = VPPApiClient
-
-# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4