blob: 1d92a41a504b2fdc585f8a0df861a2fb893cb7db [file] [log] [blame]
Ole Troana03f4ef2016-12-02 12:53:55 +01001#!/usr/bin/env python
Ole Troan5f9dcff2016-08-01 04:59:13 +02002#
3# Copyright (c) 2016 Cisco and/or its affiliates.
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at:
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
Ole Troana03f4ef2016-12-02 12:53:55 +010015#
Ole Troan5f9dcff2016-08-01 04:59:13 +020016
Ole Troan5f9dcff2016-08-01 04:59:13 +020017from __future__ import print_function
Ole Troan4df97162017-07-07 16:06:08 +020018import sys
19import os
20import logging
21import collections
22import struct
23import json
24import threading
Chris Luke52bf22e2017-11-03 23:32:38 -040025import fnmatch
Ole Troan4df97162017-07-07 16:06:08 +020026import atexit
Ole Troan3cc49712017-03-08 12:02:24 +010027from cffi import FFI
Ole Troana74b7412017-11-01 10:49:03 +010028import cffi
Ole Troan4df97162017-07-07 16:06:08 +020029
30if sys.version[0] == '2':
31 import Queue as queue
32else:
33 import queue as queue
34
Ole Troan3cc49712017-03-08 12:02:24 +010035ffi = FFI()
36ffi.cdef("""
Damjan Marion5fec1e82017-04-13 19:13:47 +020037typedef void (*vac_callback_t)(unsigned char * data, int len);
38typedef void (*vac_error_callback_t)(void *, unsigned char *, int);
39int vac_connect(char * name, char * chroot_prefix, vac_callback_t cb,
Ole Troan3cc49712017-03-08 12:02:24 +010040 int rx_qlen);
Damjan Marion5fec1e82017-04-13 19:13:47 +020041int vac_disconnect(void);
42int vac_read(char **data, int *l, unsigned short timeout);
43int vac_write(char *data, int len);
44void vac_free(void * msg);
Ole Troan5f9dcff2016-08-01 04:59:13 +020045
Damjan Marion5fec1e82017-04-13 19:13:47 +020046int vac_get_msg_index(unsigned char * name);
47int vac_msg_table_size(void);
48int vac_msg_table_max_index(void);
Ole Troan3cc49712017-03-08 12:02:24 +010049
Damjan Marion5fec1e82017-04-13 19:13:47 +020050void vac_rx_suspend (void);
51void vac_rx_resume (void);
52void vac_set_error_handler(vac_error_callback_t);
Ole Troan3cc49712017-03-08 12:02:24 +010053 """)
54
55# Barfs on failure, no need to check success.
Damjan Marion5fec1e82017-04-13 19:13:47 +020056vpp_api = ffi.dlopen('libvppapiclient.so')
Ole Troan1732fc12016-08-30 21:03:51 +020057
Ole Troan5016f992017-01-19 09:44:44 +010058def vpp_atexit(self):
59 """Clean up VPP connection on shutdown."""
60 if self.connected:
Ole Troan3cc49712017-03-08 12:02:24 +010061 self.logger.debug('Cleaning up VPP on exit')
Ole Troan5016f992017-01-19 09:44:44 +010062 self.disconnect()
63
Ole Troan3cc49712017-03-08 12:02:24 +010064vpp_object = None
65
Ole Troan4df97162017-07-07 16:06:08 +020066
67def vpp_iterator(d):
68 if sys.version[0] == '2':
69 return d.iteritems()
70 else:
71 return d.items()
72
73
Ole Troan3cc49712017-03-08 12:02:24 +010074@ffi.callback("void(unsigned char *, int)")
Damjan Marion5fec1e82017-04-13 19:13:47 +020075def vac_callback_sync(data, len):
Ole Troan3cc49712017-03-08 12:02:24 +010076 vpp_object.msg_handler_sync(ffi.buffer(data, len))
Ole Troan4df97162017-07-07 16:06:08 +020077
78
Ole Troan3cc49712017-03-08 12:02:24 +010079@ffi.callback("void(unsigned char *, int)")
Damjan Marion5fec1e82017-04-13 19:13:47 +020080def vac_callback_async(data, len):
Ole Troan3cc49712017-03-08 12:02:24 +010081 vpp_object.msg_handler_async(ffi.buffer(data, len))
Ole Troan4df97162017-07-07 16:06:08 +020082
83
Ole Troan3cc49712017-03-08 12:02:24 +010084@ffi.callback("void(void *, unsigned char *, int)")
Damjan Marion5fec1e82017-04-13 19:13:47 +020085def vac_error_handler(arg, msg, msg_len):
Ole Troan4df97162017-07-07 16:06:08 +020086 vpp_object.logger.warning("VPP API client:: %s", ffi.string(msg, msg_len))
87
Klement Sekera7112c542017-03-01 09:53:19 +010088
89class Empty(object):
90 pass
91
92
93class FuncWrapper(object):
94 def __init__(self, func):
95 self._func = func
96 self.__name__ = func.__name__
97
98 def __call__(self, **kwargs):
99 return self._func(**kwargs)
100
101
Ole Troana03f4ef2016-12-02 12:53:55 +0100102class VPP():
Ole Troan5016f992017-01-19 09:44:44 +0100103 """VPP interface.
104
105 This class provides the APIs to VPP. The APIs are loaded
106 from provided .api.json files and makes functions accordingly.
107 These functions are documented in the VPP .api files, as they
108 are dynamically created.
109
110 Additionally, VPP can send callback messages; this class
111 provides a means to register a callback function to receive
112 these messages in a background thread.
113 """
Ole Troan4df97162017-07-07 16:06:08 +0200114 def __init__(self, apifiles=None, testmode=False, async_thread=True,
dongjuan84937522017-11-09 14:46:36 +0800115 logger=logging.getLogger('vpp_papi'), loglevel='debug', read_timeout=0):
Ole Troan5016f992017-01-19 09:44:44 +0100116 """Create a VPP API object.
117
118 apifiles is a list of files containing API
119 descriptions that will be loaded - methods will be
120 dynamically created reflecting these APIs. If not
121 provided this will load the API files from VPP's
122 default install location.
123 """
Ole Troan3cc49712017-03-08 12:02:24 +0100124 global vpp_object
125 vpp_object = self
126 self.logger = logger
127 logging.basicConfig(level=getattr(logging, loglevel.upper()))
128
Ole Troana03f4ef2016-12-02 12:53:55 +0100129 self.messages = {}
130 self.id_names = []
131 self.id_msgdef = []
Ole Troana03f4ef2016-12-02 12:53:55 +0100132 self.connected = False
133 self.header = struct.Struct('>HI')
Ole Troan5016f992017-01-19 09:44:44 +0100134 self.apifiles = []
Ole Troan3d31f002017-01-26 11:13:00 +0100135 self.event_callback = None
Ole Troan4df97162017-07-07 16:06:08 +0200136 self.message_queue = queue.Queue()
dongjuan84937522017-11-09 14:46:36 +0800137 self.read_timeout = read_timeout
Ole Troandfc9b7c2017-03-06 23:51:57 +0100138 self.vpp_api = vpp_api
139 if async_thread:
Ole Troan4df97162017-07-07 16:06:08 +0200140 self.event_thread = threading.Thread(
141 target=self.thread_msg_handler)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100142 self.event_thread.daemon = True
143 self.event_thread.start()
Ole Troan5f9dcff2016-08-01 04:59:13 +0200144
Ole Troanf5984bd2016-12-18 13:15:08 +0100145 if not apifiles:
146 # Pick up API definitions from default directory
Chris Luke52bf22e2017-11-03 23:32:38 -0400147 try:
148 apifiles = self.find_api_files()
149 except RuntimeError:
150 # In test mode we don't care that we can't find the API files
151 if testmode:
152 apifiles = []
153 else:
154 raise
Ole Troanf5984bd2016-12-18 13:15:08 +0100155
Ole Troana03f4ef2016-12-02 12:53:55 +0100156 for file in apifiles:
Ole Troana03f4ef2016-12-02 12:53:55 +0100157 with open(file) as apidef_file:
158 api = json.load(apidef_file)
159 for t in api['types']:
160 self.add_type(t[0], t[1:])
Ole Troan5f9dcff2016-08-01 04:59:13 +0200161
Ole Troana03f4ef2016-12-02 12:53:55 +0100162 for m in api['messages']:
163 self.add_message(m[0], m[1:])
Ole Troan4df97162017-07-07 16:06:08 +0200164 self.apifiles = apifiles
Ole Troan5f9dcff2016-08-01 04:59:13 +0200165
Ole Troana03f4ef2016-12-02 12:53:55 +0100166 # Basic sanity check
Ole Troanf5984bd2016-12-18 13:15:08 +0100167 if len(self.messages) == 0 and not testmode:
168 raise ValueError(1, 'Missing JSON message definitions')
Ole Troan5f9dcff2016-08-01 04:59:13 +0200169
Ole Troan5016f992017-01-19 09:44:44 +0100170 # Make sure we allow VPP to clean up the message rings.
171 atexit.register(vpp_atexit, self)
Ole Troan5f9dcff2016-08-01 04:59:13 +0200172
Ole Troan3cc49712017-03-08 12:02:24 +0100173 # Register error handler
Damjan Marion5fec1e82017-04-13 19:13:47 +0200174 vpp_api.vac_set_error_handler(vac_error_handler)
Ole Troan3cc49712017-03-08 12:02:24 +0100175
Ole Troana74b7412017-11-01 10:49:03 +0100176 # Support legacy CFFI
177 # from_buffer supported from 1.8.0
178 (major, minor, patch) = [int(s) for s in cffi.__version__.split('.', 3)]
179 if major >= 1 and minor >= 8:
180 self._write = self._write_new_cffi
181 else:
182 self._write = self._write_legacy_cffi
183
Ole Troana03f4ef2016-12-02 12:53:55 +0100184 class ContextId(object):
Ole Troan5016f992017-01-19 09:44:44 +0100185 """Thread-safe provider of unique context IDs."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100186 def __init__(self):
187 self.context = 0
Ole Troan4df97162017-07-07 16:06:08 +0200188 self.lock = threading.Lock()
189
Ole Troana03f4ef2016-12-02 12:53:55 +0100190 def __call__(self):
Ole Troan5016f992017-01-19 09:44:44 +0100191 """Get a new unique (or, at least, not recently used) context."""
Ole Troan4df97162017-07-07 16:06:08 +0200192 with self.lock:
193 self.context += 1
194 return self.context
Ole Troana03f4ef2016-12-02 12:53:55 +0100195 get_context = ContextId()
Ole Troan5f9dcff2016-08-01 04:59:13 +0200196
Chris Luke52bf22e2017-11-03 23:32:38 -0400197 @classmethod
198 def find_api_dir(cls):
199 """Attempt to find the best directory in which API definition
200 files may reside. If the value VPP_API_DIR exists in the environment
201 then it is first on the search list. If we're inside a recognized
202 location in a VPP source tree (src/scripts and src/vpp-api/python)
203 then entries from there to the likely locations in build-root are
204 added. Finally the location used by system packages is added.
205
206 :returns: A single directory name, or None if no such directory
207 could be found.
208 """
209 dirs = []
210
211 if 'VPP_API_DIR' in os.environ:
212 dirs.append(os.environ['VPP_API_DIR'])
213
214 # perhaps we're in the 'src/scripts' or 'src/vpp-api/python' dir;
215 # in which case, plot a course to likely places in the src tree
216 import __main__ as main
217 if hasattr(main, '__file__'):
218 # get the path of the calling script
219 localdir = os.path.dirname(os.path.realpath(main.__file__))
220 else:
221 # use cwd if there is no calling script
222 localdir = os.cwd()
223 localdir_s = localdir.split(os.path.sep)
224
225 def dmatch(dir):
226 """Match dir against right-hand components of the script dir"""
227 d = dir.split('/') # param 'dir' assumes a / separator
228 l = len(d)
229 return len(localdir_s) > l and localdir_s[-l:] == d
230
231 def sdir(srcdir, variant):
232 """Build a path from srcdir to the staged API files of
233 'variant' (typically '' or '_debug')"""
234 # Since 'core' and 'plugin' files are staged
235 # in separate directories, we target the parent dir.
236 return os.path.sep.join((
237 srcdir,
238 'build-root',
239 'install-vpp%s-native' % variant,
240 'vpp',
241 'share',
242 'vpp',
243 'api',
244 ))
245
246 srcdir = None
247 if dmatch('src/scripts'):
248 srcdir = os.path.sep.join(localdir_s[:-2])
249 elif dmatch('src/vpp-api/python'):
250 srcdir = os.path.sep.join(localdir_s[:-3])
251 elif dmatch('test'):
252 # we're apparently running tests
253 srcdir = os.path.sep.join(localdir_s[:-1])
254
255 if srcdir:
256 # we're in the source tree, try both the debug and release
257 # variants.
258 x = 'vpp/share/vpp/api'
259 dirs.append(sdir(srcdir, '_debug'))
260 dirs.append(sdir(srcdir, ''))
261
262 # Test for staged copies of the scripts
263 # For these, since we explicitly know if we're running a debug versus
264 # release variant, target only the relevant directory
265 if dmatch('build-root/install-vpp_debug-native/vpp/bin'):
266 srcdir = os.path.sep.join(localdir_s[:-4])
267 dirs.append(sdir(srcdir, '_debug'))
268 if dmatch('build-root/install-vpp-native/vpp/bin'):
269 srcdir = os.path.sep.join(localdir_s[:-4])
270 dirs.append(sdir(srcdir, ''))
271
272 # finally, try the location system packages typically install into
273 dirs.append(os.path.sep.join(('', 'usr', 'share', 'vpp', 'api')))
274
275 # check the directories for existance; first one wins
276 for dir in dirs:
277 if os.path.isdir(dir):
278 return dir
279
280 return None
281
282 @classmethod
283 def find_api_files(cls, api_dir=None, patterns='*'):
284 """Find API definition files from the given directory tree with the
285 given pattern. If no directory is given then find_api_dir() is used
286 to locate one. If no pattern is given then all definition files found
287 in the directory tree are used.
288
289 :param api_dir: A directory tree in which to locate API definition
290 files; subdirectories are descended into.
291 If this is None then find_api_dir() is called to discover it.
292 :param patterns: A list of patterns to use in each visited directory
293 when looking for files.
294 This can be a list/tuple object or a comma-separated string of
295 patterns. Each value in the list will have leading/trialing
296 whitespace stripped.
297 The pattern specifies the first part of the filename, '.api.json'
298 is appended.
299 The results are de-duplicated, thus overlapping patterns are fine.
300 If this is None it defaults to '*' meaning "all API files".
301 :returns: A list of file paths for the API files found.
302 """
303 if api_dir is None:
304 api_dir = cls.find_api_dir()
305 if api_dir is None:
306 raise RuntimeError("api_dir cannot be located")
307
308 if isinstance(patterns, list) or isinstance(patterns, tuple):
309 patterns = [p.strip() + '.api.json' for p in patterns]
310 else:
311 patterns = [p.strip() + '.api.json' for p in patterns.split(",")]
312
313 api_files = []
314 for root, dirnames, files in os.walk(api_dir):
315 # iterate all given patterns and de-dup the result
316 files = set(sum([fnmatch.filter(files, p) for p in patterns], []))
317 for filename in files:
318 api_files.append(os.path.join(root, filename))
319
320 return api_files
321
Ole Troana03f4ef2016-12-02 12:53:55 +0100322 def status(self):
Ole Troan5016f992017-01-19 09:44:44 +0100323 """Debug function: report current VPP API status to stdout."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100324 print('Connected') if self.connected else print('Not Connected')
Ole Troan5016f992017-01-19 09:44:44 +0100325 print('Read API definitions from', ', '.join(self.apifiles))
Ole Troan5f9dcff2016-08-01 04:59:13 +0200326
Ole Troan4df97162017-07-07 16:06:08 +0200327 def __struct(self, t, n=None, e=-1, vl=None):
Ole Troan5016f992017-01-19 09:44:44 +0100328 """Create a packing structure for a message."""
Ole Troan4df97162017-07-07 16:06:08 +0200329 base_types = {'u8': 'B',
330 'u16': 'H',
331 'u32': 'I',
332 'i32': 'i',
333 'u64': 'Q',
334 'f64': 'd', }
Ole Troana03f4ef2016-12-02 12:53:55 +0100335 pack = None
336 if t in base_types:
337 pack = base_types[t]
338 if not vl:
Ole Troanf5984bd2016-12-18 13:15:08 +0100339 if e > 0 and t == 'u8':
Ole Troana03f4ef2016-12-02 12:53:55 +0100340 # Fixed byte array
Ole Troan895b6e82017-10-20 13:28:20 +0200341 s = struct.Struct('>' + str(e) + 's')
342 return s.size, s
Ole Troanf5984bd2016-12-18 13:15:08 +0100343 if e > 0:
Ole Troana03f4ef2016-12-02 12:53:55 +0100344 # Fixed array of base type
Ole Troan895b6e82017-10-20 13:28:20 +0200345 s = struct.Struct('>' + base_types[t])
346 return s.size, [e, s]
Ole Troanf5984bd2016-12-18 13:15:08 +0100347 elif e == 0:
348 # Old style variable array
Ole Troan895b6e82017-10-20 13:28:20 +0200349 s = struct.Struct('>' + base_types[t])
350 return s.size, [-1, s]
Ole Troana03f4ef2016-12-02 12:53:55 +0100351 else:
352 # Variable length array
Ole Troan895b6e82017-10-20 13:28:20 +0200353 if t == 'u8':
354 s = struct.Struct('>s')
355 return s.size, [vl, s]
356 else:
357 s = struct.Struct('>' + base_types[t])
358 return s.size, [vl, s]
Ole Troan57c3d662016-09-12 22:00:32 +0200359
Ole Troan895b6e82017-10-20 13:28:20 +0200360 s = struct.Struct('>' + base_types[t])
361 return s.size, s
Ole Troan57c3d662016-09-12 22:00:32 +0200362
Ole Troana03f4ef2016-12-02 12:53:55 +0100363 if t in self.messages:
Ole Troan895b6e82017-10-20 13:28:20 +0200364 size = self.messages[t]['sizes'][0]
365
Ole Troan4df97162017-07-07 16:06:08 +0200366 # Return a list in case of array
Ole Troanf5984bd2016-12-18 13:15:08 +0100367 if e > 0 and not vl:
Ole Troan895b6e82017-10-20 13:28:20 +0200368 return size, [e, lambda self, encode, buf, offset, args: (
Ole Troanf5984bd2016-12-18 13:15:08 +0100369 self.__struct_type(encode, self.messages[t], buf, offset,
370 args))]
Ole Troana03f4ef2016-12-02 12:53:55 +0100371 if vl:
Ole Troan895b6e82017-10-20 13:28:20 +0200372 return size, [vl, lambda self, encode, buf, offset, args: (
Ole Troanf5984bd2016-12-18 13:15:08 +0100373 self.__struct_type(encode, self.messages[t], buf, offset,
374 args))]
375 elif e == 0:
376 # Old style VLA
Ole Troan4df97162017-07-07 16:06:08 +0200377 raise NotImplementedError(1,
378 'No support for compound types ' + t)
Ole Troan895b6e82017-10-20 13:28:20 +0200379 return size, lambda self, encode, buf, offset, args: (
Ole Troana03f4ef2016-12-02 12:53:55 +0100380 self.__struct_type(encode, self.messages[t], buf, offset, args)
381 )
Ole Troanb8602b52016-10-05 11:10:50 +0200382
Ole Troanf5984bd2016-12-18 13:15:08 +0100383 raise ValueError(1, 'Invalid message type: ' + t)
Ole Troan5f9dcff2016-08-01 04:59:13 +0200384
Ole Troana03f4ef2016-12-02 12:53:55 +0100385 def __struct_type(self, encode, msgdef, buf, offset, kwargs):
Ole Troan5016f992017-01-19 09:44:44 +0100386 """Get a message packer or unpacker."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100387 if encode:
388 return self.__struct_type_encode(msgdef, buf, offset, kwargs)
389 else:
390 return self.__struct_type_decode(msgdef, buf, offset)
Ole Troan5f9dcff2016-08-01 04:59:13 +0200391
Ole Troana03f4ef2016-12-02 12:53:55 +0100392 def __struct_type_encode(self, msgdef, buf, offset, kwargs):
393 off = offset
394 size = 0
Ole Troan7e3a8752016-12-05 10:27:09 +0100395
396 for k in kwargs:
397 if k not in msgdef['args']:
Ole Troan68ec9402017-08-31 13:18:44 +0200398 raise ValueError(1,'Non existing argument [' + k + ']' + \
399 ' used in call to: ' + \
400 self.id_names[kwargs['_vl_msg_id']] + '()' )
401
Ole Troan4df97162017-07-07 16:06:08 +0200402 for k, v in vpp_iterator(msgdef['args']):
Ole Troana03f4ef2016-12-02 12:53:55 +0100403 off += size
404 if k in kwargs:
405 if type(v) is list:
406 if callable(v[1]):
Ole Troanf5984bd2016-12-18 13:15:08 +0100407 e = kwargs[v[0]] if v[0] in kwargs else v[0]
Ole Troan895b6e82017-10-20 13:28:20 +0200408 if e != len(kwargs[k]):
409 raise (ValueError(1, 'Input list length mismatch: %s (%s != %s)' % (k, e, len(kwargs[k]))))
Ole Troana03f4ef2016-12-02 12:53:55 +0100410 size = 0
411 for i in range(e):
412 size += v[1](self, True, buf, off + size,
413 kwargs[k][i])
414 else:
415 if v[0] in kwargs:
416 l = kwargs[v[0]]
Ole Troan895b6e82017-10-20 13:28:20 +0200417 if l != len(kwargs[k]):
Chris Luke52bf22e2017-11-03 23:32:38 -0400418 raise ValueError(1, 'Input list length mismatch: %s (%s != %s)' % (k, l, len(kwargs[k])))
Ole Troana03f4ef2016-12-02 12:53:55 +0100419 else:
420 l = len(kwargs[k])
421 if v[1].size == 1:
422 buf[off:off + l] = bytearray(kwargs[k])
423 size = l
424 else:
425 size = 0
426 for i in kwargs[k]:
427 v[1].pack_into(buf, off + size, i)
428 size += v[1].size
429 else:
430 if callable(v):
431 size = v(self, True, buf, off, kwargs[k])
432 else:
Ole Troan895b6e82017-10-20 13:28:20 +0200433 if type(kwargs[k]) is str and v.size < len(kwargs[k]):
Chris Luke52bf22e2017-11-03 23:32:38 -0400434 raise ValueError(1, 'Input list length mismatch: %s (%s < %s)' % (k, v.size, len(kwargs[k])))
Ole Troana03f4ef2016-12-02 12:53:55 +0100435 v.pack_into(buf, off, kwargs[k])
436 size = v.size
437 else:
Ole Troan7e3a8752016-12-05 10:27:09 +0100438 size = v.size if not type(v) is list else 0
Ole Troan5f9dcff2016-08-01 04:59:13 +0200439
Ole Troana03f4ef2016-12-02 12:53:55 +0100440 return off + size - offset
Ole Troan5f9dcff2016-08-01 04:59:13 +0200441
Ole Troana03f4ef2016-12-02 12:53:55 +0100442 def __getitem__(self, name):
443 if name in self.messages:
444 return self.messages[name]
445 return None
Ole Troan5f9dcff2016-08-01 04:59:13 +0200446
Ole Troan895b6e82017-10-20 13:28:20 +0200447 def get_size(self, sizes, kwargs):
448 total_size = sizes[0]
449 for e in sizes[1]:
450 if e in kwargs and type(kwargs[e]) is list:
451 total_size += len(kwargs[e]) * sizes[1][e]
452 return total_size
453
Ole Troana03f4ef2016-12-02 12:53:55 +0100454 def encode(self, msgdef, kwargs):
455 # Make suitably large buffer
Chris Luke52bf22e2017-11-03 23:32:38 -0400456 size = self.get_size(msgdef['sizes'], kwargs)
Ole Troan895b6e82017-10-20 13:28:20 +0200457 buf = bytearray(size)
Ole Troana03f4ef2016-12-02 12:53:55 +0100458 offset = 0
459 size = self.__struct_type(True, msgdef, buf, offset, kwargs)
460 return buf[:offset + size]
461
462 def decode(self, msgdef, buf):
463 return self.__struct_type(False, msgdef, buf, 0, None)[1]
464
465 def __struct_type_decode(self, msgdef, buf, offset):
466 res = []
467 off = offset
468 size = 0
Ole Troan4df97162017-07-07 16:06:08 +0200469 for k, v in vpp_iterator(msgdef['args']):
Ole Troana03f4ef2016-12-02 12:53:55 +0100470 off += size
471 if type(v) is list:
472 lst = []
Ole Troan4df97162017-07-07 16:06:08 +0200473 if callable(v[1]): # compound type
Ole Troana03f4ef2016-12-02 12:53:55 +0100474 size = 0
Ole Troan4df97162017-07-07 16:06:08 +0200475 if v[0] in msgdef['args']: # vla
Ole Troanf5984bd2016-12-18 13:15:08 +0100476 e = res[v[2]]
Ole Troan4df97162017-07-07 16:06:08 +0200477 else: # fixed array
Ole Troana03f4ef2016-12-02 12:53:55 +0100478 e = v[0]
479 res.append(lst)
480 for i in range(e):
Ole Troan4df97162017-07-07 16:06:08 +0200481 (s, l) = v[1](self, False, buf, off + size, None)
Ole Troana03f4ef2016-12-02 12:53:55 +0100482 lst.append(l)
483 size += s
484 continue
Ole Troana03f4ef2016-12-02 12:53:55 +0100485 if v[1].size == 1:
Ole Troanf5984bd2016-12-18 13:15:08 +0100486 if type(v[0]) is int:
487 size = len(buf) - off
488 else:
489 size = res[v[2]]
Ole Troana03f4ef2016-12-02 12:53:55 +0100490 res.append(buf[off:off + size])
491 else:
Ole Troanf5984bd2016-12-18 13:15:08 +0100492 e = v[0] if type(v[0]) is int else res[v[2]]
493 if e == -1:
494 e = (len(buf) - off) / v[1].size
Ole Troana03f4ef2016-12-02 12:53:55 +0100495 lst = []
496 res.append(lst)
497 size = 0
498 for i in range(e):
499 lst.append(v[1].unpack_from(buf, off + size)[0])
500 size += v[1].size
501 else:
502 if callable(v):
Ole Troanca3b6f12017-10-31 14:50:13 +0100503 size = 0
Ole Troan4df97162017-07-07 16:06:08 +0200504 (s, l) = v(self, False, buf, off, None)
Ole Troana03f4ef2016-12-02 12:53:55 +0100505 res.append(l)
506 size += s
507 else:
508 res.append(v.unpack_from(buf, off)[0])
509 size = v.size
510
511 return off + size - offset, msgdef['return_tuple']._make(res)
512
513 def ret_tup(self, name):
514 if name in self.messages and 'return_tuple' in self.messages[name]:
515 return self.messages[name]['return_tuple']
516 return None
517
Ole Troan2eaf2d22018-01-25 09:53:31 +0100518 def duplicate_check_ok(self, name, msgdef):
519 crc = None
520 for c in msgdef:
521 if type(c) is dict and 'crc' in c:
522 crc = c['crc']
523 break
524 if crc:
525 # We can get duplicates because of imports
526 if crc == self.messages[name]['crc']:
527 return True
528 return False
529
Ole Troan4df97162017-07-07 16:06:08 +0200530 def add_message(self, name, msgdef, typeonly=False):
Ole Troana03f4ef2016-12-02 12:53:55 +0100531 if name in self.messages:
Ole Troan2eaf2d22018-01-25 09:53:31 +0100532 if typeonly:
533 if self.duplicate_check_ok(name, msgdef):
534 return
Ole Troana03f4ef2016-12-02 12:53:55 +0100535 raise ValueError('Duplicate message name: ' + name)
536
537 args = collections.OrderedDict()
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100538 argtypes = collections.OrderedDict()
Ole Troana03f4ef2016-12-02 12:53:55 +0100539 fields = []
540 msg = {}
Ole Troan895b6e82017-10-20 13:28:20 +0200541 total_size = 0
542 sizes = {}
Ole Troanf5984bd2016-12-18 13:15:08 +0100543 for i, f in enumerate(msgdef):
Ole Troana03f4ef2016-12-02 12:53:55 +0100544 if type(f) is dict and 'crc' in f:
545 msg['crc'] = f['crc']
546 continue
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100547 field_type = f[0]
Ole Troana03f4ef2016-12-02 12:53:55 +0100548 field_name = f[1]
Ole Troanf5984bd2016-12-18 13:15:08 +0100549 if len(f) == 3 and f[2] == 0 and i != len(msgdef) - 2:
550 raise ValueError('Variable Length Array must be last: ' + name)
Ole Troan895b6e82017-10-20 13:28:20 +0200551 size, s = self.__struct(*f)
552 args[field_name] = s
553 if type(s) == list and type(s[0]) == int and type(s[1]) == struct.Struct:
554 if s[0] < 0:
555 sizes[field_name] = size
556 else:
557 sizes[field_name] = size
558 total_size += s[0] * size
559 else:
560 sizes[field_name] = size
561 total_size += size
562
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100563 argtypes[field_name] = field_type
Ole Troan4df97162017-07-07 16:06:08 +0200564 if len(f) == 4: # Find offset to # elements field
565 idx = list(args.keys()).index(f[3]) - i
566 args[field_name].append(idx)
Ole Troana03f4ef2016-12-02 12:53:55 +0100567 fields.append(field_name)
568 msg['return_tuple'] = collections.namedtuple(name, fields,
Ole Troan4df97162017-07-07 16:06:08 +0200569 rename=True)
Ole Troana03f4ef2016-12-02 12:53:55 +0100570 self.messages[name] = msg
571 self.messages[name]['args'] = args
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100572 self.messages[name]['argtypes'] = argtypes
Ole Troan3cc49712017-03-08 12:02:24 +0100573 self.messages[name]['typeonly'] = typeonly
Ole Troan895b6e82017-10-20 13:28:20 +0200574 self.messages[name]['sizes'] = [total_size, sizes]
Ole Troana03f4ef2016-12-02 12:53:55 +0100575 return self.messages[name]
576
577 def add_type(self, name, typedef):
Ole Troan4df97162017-07-07 16:06:08 +0200578 return self.add_message('vl_api_' + name + '_t', typedef,
579 typeonly=True)
Ole Troana03f4ef2016-12-02 12:53:55 +0100580
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100581 def make_function(self, name, i, msgdef, multipart, async):
Wojciech Dec64bc6122016-12-12 11:32:25 +0100582 if (async):
Ole Troan5016f992017-01-19 09:44:44 +0100583 f = lambda **kwargs: (self._call_vpp_async(i, msgdef, **kwargs))
Wojciech Dec64bc6122016-12-12 11:32:25 +0100584 else:
Ole Troan4df97162017-07-07 16:06:08 +0200585 f = lambda **kwargs: (self._call_vpp(i, msgdef, multipart,
586 **kwargs))
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100587 args = self.messages[name]['args']
588 argtypes = self.messages[name]['argtypes']
589 f.__name__ = str(name)
Ole Troan4df97162017-07-07 16:06:08 +0200590 f.__doc__ = ", ".join(["%s %s" %
591 (argtypes[k], k) for k in args.keys()])
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100592 return f
Ole Troana03f4ef2016-12-02 12:53:55 +0100593
Klement Sekera7112c542017-03-01 09:53:19 +0100594 @property
595 def api(self):
596 if not hasattr(self, "_api"):
597 raise Exception("Not connected, api definitions not available")
598 return self._api
599
Wojciech Dec64bc6122016-12-12 11:32:25 +0100600 def _register_functions(self, async=False):
Ole Troana03f4ef2016-12-02 12:53:55 +0100601 self.id_names = [None] * (self.vpp_dictionary_maxid + 1)
602 self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1)
Klement Sekera7112c542017-03-01 09:53:19 +0100603 self._api = Empty()
Ole Troan4df97162017-07-07 16:06:08 +0200604 for name, msgdef in vpp_iterator(self.messages):
605 if self.messages[name]['typeonly']:
606 continue
Ole Troan3cc49712017-03-08 12:02:24 +0100607 crc = self.messages[name]['crc']
608 n = name + '_' + crc[2:]
Ole Troan4df97162017-07-07 16:06:08 +0200609 i = vpp_api.vac_get_msg_index(n.encode())
Ole Troan3cc49712017-03-08 12:02:24 +0100610 if i > 0:
Ole Troana03f4ef2016-12-02 12:53:55 +0100611 self.id_msgdef[i] = msgdef
612 self.id_names[i] = name
613 multipart = True if name.find('_dump') > 0 else False
Klement Sekera7112c542017-03-01 09:53:19 +0100614 f = self.make_function(name, i, msgdef, multipart, async)
615 setattr(self._api, name, FuncWrapper(f))
616
Ole Troandfc9b7c2017-03-06 23:51:57 +0100617 # old API stuff starts here - will be removed in 17.07
Klement Sekera7112c542017-03-01 09:53:19 +0100618 if hasattr(self, name):
619 raise NameError(
620 3, "Conflicting name in JSON definition: `%s'" % name)
621 setattr(self, name, f)
622 # old API stuff ends here
Ole Troan3cc49712017-03-08 12:02:24 +0100623 else:
Ole Troan4df97162017-07-07 16:06:08 +0200624 self.logger.debug(
625 'No such message type or failed CRC checksum: %s', n)
Ole Troana03f4ef2016-12-02 12:53:55 +0100626
Ole Troana74b7412017-11-01 10:49:03 +0100627 def _write_new_cffi(self, buf):
Ole Troan5016f992017-01-19 09:44:44 +0100628 """Send a binary-packed message to VPP."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100629 if not self.connected:
630 raise IOError(1, 'Not connected')
Ole Troan4df97162017-07-07 16:06:08 +0200631 return vpp_api.vac_write(ffi.from_buffer(buf), len(buf))
Ole Troana03f4ef2016-12-02 12:53:55 +0100632
Ole Troana74b7412017-11-01 10:49:03 +0100633 def _write_legacy_cffi(self, buf):
634 """Send a binary-packed message to VPP."""
635 if not self.connected:
636 raise IOError(1, 'Not connected')
637 return vpp_api.vac_write(str(buf), len(buf))
638
Ole Troan4df97162017-07-07 16:06:08 +0200639 def _read(self):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100640 if not self.connected:
641 raise IOError(1, 'Not connected')
Ole Troan3cc49712017-03-08 12:02:24 +0100642 mem = ffi.new("char **")
643 size = ffi.new("int *")
Damjan Marion5fec1e82017-04-13 19:13:47 +0200644 rv = vpp_api.vac_read(mem, size, self.read_timeout)
Ole Troan3cc49712017-03-08 12:02:24 +0100645 if rv:
Ole Troanb0856b42017-08-17 12:48:08 +0200646 raise IOError(rv, 'vac_read failed')
Ole Troan3cc49712017-03-08 12:02:24 +0100647 msg = bytes(ffi.buffer(mem[0], size[0]))
Damjan Marion5fec1e82017-04-13 19:13:47 +0200648 vpp_api.vac_free(mem[0])
Ole Troan3cc49712017-03-08 12:02:24 +0100649 return msg
Ole Troana03f4ef2016-12-02 12:53:55 +0100650
Ole Troan4df97162017-07-07 16:06:08 +0200651 def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen,
652 async):
Ole Troan6bf177c2017-08-17 10:34:32 +0200653 pfx = chroot_prefix.encode() if chroot_prefix else ffi.NULL
654 rv = vpp_api.vac_connect(name.encode(), pfx, msg_handler, rx_qlen)
Ole Troana03f4ef2016-12-02 12:53:55 +0100655 if rv != 0:
656 raise IOError(2, 'Connect failed')
Ole Troan7e3a8752016-12-05 10:27:09 +0100657 self.connected = True
Ole Troana03f4ef2016-12-02 12:53:55 +0100658
Damjan Marion5fec1e82017-04-13 19:13:47 +0200659 self.vpp_dictionary_maxid = vpp_api.vac_msg_table_max_index()
Wojciech Dec64bc6122016-12-12 11:32:25 +0100660 self._register_functions(async=async)
Ole Troana03f4ef2016-12-02 12:53:55 +0100661
662 # Initialise control ping
Ole Troan3cc49712017-03-08 12:02:24 +0100663 crc = self.messages['control_ping']['crc']
Ole Troan4df97162017-07-07 16:06:08 +0200664 self.control_ping_index = vpp_api.vac_get_msg_index(
665 ('control_ping' + '_' + crc[2:]).encode())
Ole Troana03f4ef2016-12-02 12:53:55 +0100666 self.control_ping_msgdef = self.messages['control_ping']
Ole Troan4df97162017-07-07 16:06:08 +0200667 return rv
Ole Troana03f4ef2016-12-02 12:53:55 +0100668
Ole Troan6bf177c2017-08-17 10:34:32 +0200669 def connect(self, name, chroot_prefix=None, async=False, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100670 """Attach to VPP.
671
672 name - the name of the client.
673 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
674 async - if true, messages are sent without waiting for a reply
675 rx_qlen - the length of the VPP message receive queue between
676 client and server.
677 """
Ole Troan4df97162017-07-07 16:06:08 +0200678 msg_handler = vac_callback_sync if not async else vac_callback_async
Ole Troandfc9b7c2017-03-06 23:51:57 +0100679 return self.connect_internal(name, msg_handler, chroot_prefix, rx_qlen,
680 async)
681
Ole Troan6bf177c2017-08-17 10:34:32 +0200682 def connect_sync(self, name, chroot_prefix=None, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100683 """Attach to VPP in synchronous mode. Application must poll for events.
684
685 name - the name of the client.
686 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
687 rx_qlen - the length of the VPP message receive queue between
688 client and server.
689 """
690
Ole Troan3cc49712017-03-08 12:02:24 +0100691 return self.connect_internal(name, ffi.NULL, chroot_prefix, rx_qlen,
Ole Troandfc9b7c2017-03-06 23:51:57 +0100692 async=False)
693
Ole Troana03f4ef2016-12-02 12:53:55 +0100694 def disconnect(self):
Ole Troan5016f992017-01-19 09:44:44 +0100695 """Detach from VPP."""
Damjan Marion5fec1e82017-04-13 19:13:47 +0200696 rv = vpp_api.vac_disconnect()
Ole Troan5016f992017-01-19 09:44:44 +0100697 self.connected = False
Ole Troana03f4ef2016-12-02 12:53:55 +0100698 return rv
699
Ole Troan5016f992017-01-19 09:44:44 +0100700 def msg_handler_sync(self, msg):
701 """Process an incoming message from VPP in sync mode.
702
703 The message may be a reply or it may be an async notification.
704 """
705 r = self.decode_incoming_msg(msg)
706 if r is None:
Ole Troana03f4ef2016-12-02 12:53:55 +0100707 return
708
Ole Troan5016f992017-01-19 09:44:44 +0100709 # If we have a context, then use the context to find any
710 # request waiting for a reply
711 context = 0
712 if hasattr(r, 'context') and r.context > 0:
713 context = r.context
Ole Troan5f9dcff2016-08-01 04:59:13 +0200714
Ole Troana03f4ef2016-12-02 12:53:55 +0100715 msgname = type(r).__name__
Ole Troan57c3d662016-09-12 22:00:32 +0200716
Ole Troan5016f992017-01-19 09:44:44 +0100717 if context == 0:
718 # No context -> async notification that we feed to the callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100719 self.message_queue.put_nowait(r)
Ole Troana03f4ef2016-12-02 12:53:55 +0100720 else:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100721 raise IOError(2, 'RPC reply message received in event handler')
Ole Troan5016f992017-01-19 09:44:44 +0100722
723 def decode_incoming_msg(self, msg):
724 if not msg:
Ole Troan3cc49712017-03-08 12:02:24 +0100725 self.logger.warning('vpp_api.read failed')
Ole Troan5016f992017-01-19 09:44:44 +0100726 return
727
728 i, ci = self.header.unpack_from(msg, 0)
729 if self.id_names[i] == 'rx_thread_exit':
730 return
731
732 #
733 # Decode message and returns a tuple.
734 #
735 msgdef = self.id_msgdef[i]
736 if not msgdef:
737 raise IOError(2, 'Reply message undefined')
738
739 r = self.decode(msgdef, msg)
740
Ole Troana03f4ef2016-12-02 12:53:55 +0100741 return r
742
Ole Troan5016f992017-01-19 09:44:44 +0100743 def msg_handler_async(self, msg):
744 """Process a message from VPP in async mode.
745
746 In async mode, all messages are returned to the callback.
747 """
748 r = self.decode_incoming_msg(msg)
749 if r is None:
750 return
751
752 msgname = type(r).__name__
753
Ole Troan4df97162017-07-07 16:06:08 +0200754 if self.event_callback:
755 self.event_callback(msgname, r)
Ole Troan5016f992017-01-19 09:44:44 +0100756
757 def _control_ping(self, context):
758 """Send a ping command."""
759 self._call_vpp_async(self.control_ping_index,
Ole Troan4df97162017-07-07 16:06:08 +0200760 self.control_ping_msgdef,
Ole Troan5016f992017-01-19 09:44:44 +0100761 context=context)
762
763 def _call_vpp(self, i, msgdef, multipart, **kwargs):
764 """Given a message, send the message and await a reply.
765
766 msgdef - the message packing definition
767 i - the message type index
768 multipart - True if the message returns multiple
769 messages in return.
770 context - context number - chosen at random if not
771 supplied.
772 The remainder of the kwargs are the arguments to the API call.
773
774 The return value is the message or message array containing
775 the response. It will raise an IOError exception if there was
776 no response within the timeout window.
777 """
778
Ole Troan4df97162017-07-07 16:06:08 +0200779 if 'context' not in kwargs:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100780 context = self.get_context()
781 kwargs['context'] = context
782 else:
783 context = kwargs['context']
784 kwargs['_vl_msg_id'] = i
785 b = self.encode(msgdef, kwargs)
Ole Troan5016f992017-01-19 09:44:44 +0100786
Damjan Marion5fec1e82017-04-13 19:13:47 +0200787 vpp_api.vac_rx_suspend()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100788 self._write(b)
Ole Troan5016f992017-01-19 09:44:44 +0100789
790 if multipart:
791 # Send a ping after the request - we use its response
792 # to detect that we have seen all results.
793 self._control_ping(context)
794
795 # Block until we get a reply.
Ole Troandfc9b7c2017-03-06 23:51:57 +0100796 rl = []
797 while (True):
798 msg = self._read()
799 if not msg:
Ole Troan4df97162017-07-07 16:06:08 +0200800 raise IOError(2, 'VPP API client: read failed')
Ole Troan5016f992017-01-19 09:44:44 +0100801
Ole Troandfc9b7c2017-03-06 23:51:57 +0100802 r = self.decode_incoming_msg(msg)
803 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200804 if context not in r or r.context == 0 or context != r.context:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100805 self.message_queue.put_nowait(r)
806 continue
807
808 if not multipart:
809 rl = r
810 break
811 if msgname == 'control_ping_reply':
812 break
813
814 rl.append(r)
815
Damjan Marion5fec1e82017-04-13 19:13:47 +0200816 vpp_api.vac_rx_resume()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100817
818 return rl
Ole Troan5016f992017-01-19 09:44:44 +0100819
820 def _call_vpp_async(self, i, msgdef, **kwargs):
821 """Given a message, send the message and await a reply.
822
823 msgdef - the message packing definition
824 i - the message type index
825 context - context number - chosen at random if not
826 supplied.
827 The remainder of the kwargs are the arguments to the API call.
828 """
Ole Troan4df97162017-07-07 16:06:08 +0200829 if 'context' not in kwargs:
Ole Troan7e3a8752016-12-05 10:27:09 +0100830 context = self.get_context()
831 kwargs['context'] = context
832 else:
833 context = kwargs['context']
834 kwargs['_vl_msg_id'] = i
835 b = self.encode(msgdef, kwargs)
836
837 self._write(b)
838
Ole Troana03f4ef2016-12-02 12:53:55 +0100839 def register_event_callback(self, callback):
Ole Troan5016f992017-01-19 09:44:44 +0100840 """Register a callback for async messages.
Ole Troana03f4ef2016-12-02 12:53:55 +0100841
Ole Troan5016f992017-01-19 09:44:44 +0100842 This will be called for async notifications in sync mode,
843 and all messages in async mode. In sync mode, replies to
844 requests will not come here.
845
846 callback is a fn(msg_type_name, msg_type) that will be
847 called when a message comes in. While this function is
848 executing, note that (a) you are in a background thread and
849 may wish to use threading.Lock to protect your datastructures,
850 and (b) message processing from VPP will stop (so if you take
851 a long while about it you may provoke reply timeouts or cause
852 VPP to fill the RX buffer). Passing None will disable the
853 callback.
854 """
855 self.event_callback = callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100856
857 def thread_msg_handler(self):
858 """Python thread calling the user registerd message handler.
859
860 This is to emulate the old style event callback scheme. Modern
861 clients should provide their own thread to poll the event
862 queue.
863 """
864 while True:
865 r = self.message_queue.get()
866 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200867 if self.event_callback:
868 self.event_callback(msgname, r)
Chris Luke52bf22e2017-11-03 23:32:38 -0400869
870
871# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4