blob: 2f4773ad8a524988278afeed2cc9c8070b17b84a [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
25import glob
26import 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
147 apifiles = glob.glob('/usr/share/vpp/api/*.api.json')
148
Ole Troana03f4ef2016-12-02 12:53:55 +0100149 for file in apifiles:
Ole Troana03f4ef2016-12-02 12:53:55 +0100150 with open(file) as apidef_file:
151 api = json.load(apidef_file)
152 for t in api['types']:
153 self.add_type(t[0], t[1:])
Ole Troan5f9dcff2016-08-01 04:59:13 +0200154
Ole Troana03f4ef2016-12-02 12:53:55 +0100155 for m in api['messages']:
156 self.add_message(m[0], m[1:])
Ole Troan4df97162017-07-07 16:06:08 +0200157 self.apifiles = apifiles
Ole Troan5f9dcff2016-08-01 04:59:13 +0200158
Ole Troana03f4ef2016-12-02 12:53:55 +0100159 # Basic sanity check
Ole Troanf5984bd2016-12-18 13:15:08 +0100160 if len(self.messages) == 0 and not testmode:
161 raise ValueError(1, 'Missing JSON message definitions')
Ole Troan5f9dcff2016-08-01 04:59:13 +0200162
Ole Troan5016f992017-01-19 09:44:44 +0100163 # Make sure we allow VPP to clean up the message rings.
164 atexit.register(vpp_atexit, self)
Ole Troan5f9dcff2016-08-01 04:59:13 +0200165
Ole Troan3cc49712017-03-08 12:02:24 +0100166 # Register error handler
Damjan Marion5fec1e82017-04-13 19:13:47 +0200167 vpp_api.vac_set_error_handler(vac_error_handler)
Ole Troan3cc49712017-03-08 12:02:24 +0100168
Ole Troana74b7412017-11-01 10:49:03 +0100169 # Support legacy CFFI
170 # from_buffer supported from 1.8.0
171 (major, minor, patch) = [int(s) for s in cffi.__version__.split('.', 3)]
172 if major >= 1 and minor >= 8:
173 self._write = self._write_new_cffi
174 else:
175 self._write = self._write_legacy_cffi
176
Ole Troana03f4ef2016-12-02 12:53:55 +0100177 class ContextId(object):
Ole Troan5016f992017-01-19 09:44:44 +0100178 """Thread-safe provider of unique context IDs."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100179 def __init__(self):
180 self.context = 0
Ole Troan4df97162017-07-07 16:06:08 +0200181 self.lock = threading.Lock()
182
Ole Troana03f4ef2016-12-02 12:53:55 +0100183 def __call__(self):
Ole Troan5016f992017-01-19 09:44:44 +0100184 """Get a new unique (or, at least, not recently used) context."""
Ole Troan4df97162017-07-07 16:06:08 +0200185 with self.lock:
186 self.context += 1
187 return self.context
Ole Troana03f4ef2016-12-02 12:53:55 +0100188 get_context = ContextId()
Ole Troan5f9dcff2016-08-01 04:59:13 +0200189
Ole Troana03f4ef2016-12-02 12:53:55 +0100190 def status(self):
Ole Troan5016f992017-01-19 09:44:44 +0100191 """Debug function: report current VPP API status to stdout."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100192 print('Connected') if self.connected else print('Not Connected')
Ole Troan5016f992017-01-19 09:44:44 +0100193 print('Read API definitions from', ', '.join(self.apifiles))
Ole Troan5f9dcff2016-08-01 04:59:13 +0200194
Ole Troan4df97162017-07-07 16:06:08 +0200195 def __struct(self, t, n=None, e=-1, vl=None):
Ole Troan5016f992017-01-19 09:44:44 +0100196 """Create a packing structure for a message."""
Ole Troan4df97162017-07-07 16:06:08 +0200197 base_types = {'u8': 'B',
198 'u16': 'H',
199 'u32': 'I',
200 'i32': 'i',
201 'u64': 'Q',
202 'f64': 'd', }
Ole Troana03f4ef2016-12-02 12:53:55 +0100203 pack = None
204 if t in base_types:
205 pack = base_types[t]
206 if not vl:
Ole Troanf5984bd2016-12-18 13:15:08 +0100207 if e > 0 and t == 'u8':
Ole Troana03f4ef2016-12-02 12:53:55 +0100208 # Fixed byte array
Ole Troan895b6e82017-10-20 13:28:20 +0200209 s = struct.Struct('>' + str(e) + 's')
210 return s.size, s
Ole Troanf5984bd2016-12-18 13:15:08 +0100211 if e > 0:
Ole Troana03f4ef2016-12-02 12:53:55 +0100212 # Fixed array of base type
Ole Troan895b6e82017-10-20 13:28:20 +0200213 s = struct.Struct('>' + base_types[t])
214 return s.size, [e, s]
Ole Troanf5984bd2016-12-18 13:15:08 +0100215 elif e == 0:
216 # Old style variable array
Ole Troan895b6e82017-10-20 13:28:20 +0200217 s = struct.Struct('>' + base_types[t])
218 return s.size, [-1, s]
Ole Troana03f4ef2016-12-02 12:53:55 +0100219 else:
220 # Variable length array
Ole Troan895b6e82017-10-20 13:28:20 +0200221 if t == 'u8':
222 s = struct.Struct('>s')
223 return s.size, [vl, s]
224 else:
225 s = struct.Struct('>' + base_types[t])
226 return s.size, [vl, s]
Ole Troan57c3d662016-09-12 22:00:32 +0200227
Ole Troan895b6e82017-10-20 13:28:20 +0200228 s = struct.Struct('>' + base_types[t])
229 return s.size, s
Ole Troan57c3d662016-09-12 22:00:32 +0200230
Ole Troana03f4ef2016-12-02 12:53:55 +0100231 if t in self.messages:
Ole Troan895b6e82017-10-20 13:28:20 +0200232 size = self.messages[t]['sizes'][0]
233
Ole Troan4df97162017-07-07 16:06:08 +0200234 # Return a list in case of array
Ole Troanf5984bd2016-12-18 13:15:08 +0100235 if e > 0 and not vl:
Ole Troan895b6e82017-10-20 13:28:20 +0200236 return size, [e, lambda self, encode, buf, offset, args: (
Ole Troanf5984bd2016-12-18 13:15:08 +0100237 self.__struct_type(encode, self.messages[t], buf, offset,
238 args))]
Ole Troana03f4ef2016-12-02 12:53:55 +0100239 if vl:
Ole Troan895b6e82017-10-20 13:28:20 +0200240 return size, [vl, lambda self, encode, buf, offset, args: (
Ole Troanf5984bd2016-12-18 13:15:08 +0100241 self.__struct_type(encode, self.messages[t], buf, offset,
242 args))]
243 elif e == 0:
244 # Old style VLA
Ole Troan4df97162017-07-07 16:06:08 +0200245 raise NotImplementedError(1,
246 'No support for compound types ' + t)
Ole Troan895b6e82017-10-20 13:28:20 +0200247 return size, lambda self, encode, buf, offset, args: (
Ole Troana03f4ef2016-12-02 12:53:55 +0100248 self.__struct_type(encode, self.messages[t], buf, offset, args)
249 )
Ole Troanb8602b52016-10-05 11:10:50 +0200250
Ole Troanf5984bd2016-12-18 13:15:08 +0100251 raise ValueError(1, 'Invalid message type: ' + t)
Ole Troan5f9dcff2016-08-01 04:59:13 +0200252
Ole Troana03f4ef2016-12-02 12:53:55 +0100253 def __struct_type(self, encode, msgdef, buf, offset, kwargs):
Ole Troan5016f992017-01-19 09:44:44 +0100254 """Get a message packer or unpacker."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100255 if encode:
256 return self.__struct_type_encode(msgdef, buf, offset, kwargs)
257 else:
258 return self.__struct_type_decode(msgdef, buf, offset)
Ole Troan5f9dcff2016-08-01 04:59:13 +0200259
Ole Troana03f4ef2016-12-02 12:53:55 +0100260 def __struct_type_encode(self, msgdef, buf, offset, kwargs):
261 off = offset
262 size = 0
Ole Troan7e3a8752016-12-05 10:27:09 +0100263
264 for k in kwargs:
265 if k not in msgdef['args']:
Ole Troan68ec9402017-08-31 13:18:44 +0200266 raise ValueError(1,'Non existing argument [' + k + ']' + \
267 ' used in call to: ' + \
268 self.id_names[kwargs['_vl_msg_id']] + '()' )
269
Ole Troan4df97162017-07-07 16:06:08 +0200270 for k, v in vpp_iterator(msgdef['args']):
Ole Troana03f4ef2016-12-02 12:53:55 +0100271 off += size
272 if k in kwargs:
273 if type(v) is list:
274 if callable(v[1]):
Ole Troanf5984bd2016-12-18 13:15:08 +0100275 e = kwargs[v[0]] if v[0] in kwargs else v[0]
Ole Troan895b6e82017-10-20 13:28:20 +0200276 if e != len(kwargs[k]):
277 raise (ValueError(1, 'Input list length mismatch: %s (%s != %s)' % (k, e, len(kwargs[k]))))
Ole Troana03f4ef2016-12-02 12:53:55 +0100278 size = 0
279 for i in range(e):
280 size += v[1](self, True, buf, off + size,
281 kwargs[k][i])
282 else:
283 if v[0] in kwargs:
284 l = kwargs[v[0]]
Ole Troan895b6e82017-10-20 13:28:20 +0200285 if l != len(kwargs[k]):
286 raise ValueError(1, 'Input list length mistmatch: %s (%s != %s)' % (k, l, len(kwargs[k])))
Ole Troana03f4ef2016-12-02 12:53:55 +0100287 else:
288 l = len(kwargs[k])
289 if v[1].size == 1:
290 buf[off:off + l] = bytearray(kwargs[k])
291 size = l
292 else:
293 size = 0
294 for i in kwargs[k]:
295 v[1].pack_into(buf, off + size, i)
296 size += v[1].size
297 else:
298 if callable(v):
299 size = v(self, True, buf, off, kwargs[k])
300 else:
Ole Troan895b6e82017-10-20 13:28:20 +0200301 if type(kwargs[k]) is str and v.size < len(kwargs[k]):
302 raise ValueError(1, 'Input list length mistmatch: %s (%s < %s)' % (k, v.size, len(kwargs[k])))
Ole Troana03f4ef2016-12-02 12:53:55 +0100303 v.pack_into(buf, off, kwargs[k])
304 size = v.size
305 else:
Ole Troan7e3a8752016-12-05 10:27:09 +0100306 size = v.size if not type(v) is list else 0
Ole Troan5f9dcff2016-08-01 04:59:13 +0200307
Ole Troana03f4ef2016-12-02 12:53:55 +0100308 return off + size - offset
Ole Troan5f9dcff2016-08-01 04:59:13 +0200309
Ole Troana03f4ef2016-12-02 12:53:55 +0100310 def __getitem__(self, name):
311 if name in self.messages:
312 return self.messages[name]
313 return None
Ole Troan5f9dcff2016-08-01 04:59:13 +0200314
Ole Troan895b6e82017-10-20 13:28:20 +0200315 def get_size(self, sizes, kwargs):
316 total_size = sizes[0]
317 for e in sizes[1]:
318 if e in kwargs and type(kwargs[e]) is list:
319 total_size += len(kwargs[e]) * sizes[1][e]
320 return total_size
321
Ole Troana03f4ef2016-12-02 12:53:55 +0100322 def encode(self, msgdef, kwargs):
323 # Make suitably large buffer
Ole Troan895b6e82017-10-20 13:28:20 +0200324 size = self.get_size(msgdef['sizes'], kwargs)
325 buf = bytearray(size)
Ole Troana03f4ef2016-12-02 12:53:55 +0100326 offset = 0
327 size = self.__struct_type(True, msgdef, buf, offset, kwargs)
328 return buf[:offset + size]
329
330 def decode(self, msgdef, buf):
331 return self.__struct_type(False, msgdef, buf, 0, None)[1]
332
333 def __struct_type_decode(self, msgdef, buf, offset):
334 res = []
335 off = offset
336 size = 0
Ole Troan4df97162017-07-07 16:06:08 +0200337 for k, v in vpp_iterator(msgdef['args']):
Ole Troana03f4ef2016-12-02 12:53:55 +0100338 off += size
339 if type(v) is list:
340 lst = []
Ole Troan4df97162017-07-07 16:06:08 +0200341 if callable(v[1]): # compound type
Ole Troana03f4ef2016-12-02 12:53:55 +0100342 size = 0
Ole Troan4df97162017-07-07 16:06:08 +0200343 if v[0] in msgdef['args']: # vla
Ole Troanf5984bd2016-12-18 13:15:08 +0100344 e = res[v[2]]
Ole Troan4df97162017-07-07 16:06:08 +0200345 else: # fixed array
Ole Troana03f4ef2016-12-02 12:53:55 +0100346 e = v[0]
347 res.append(lst)
348 for i in range(e):
Ole Troan4df97162017-07-07 16:06:08 +0200349 (s, l) = v[1](self, False, buf, off + size, None)
Ole Troana03f4ef2016-12-02 12:53:55 +0100350 lst.append(l)
351 size += s
352 continue
Ole Troana03f4ef2016-12-02 12:53:55 +0100353 if v[1].size == 1:
Ole Troanf5984bd2016-12-18 13:15:08 +0100354 if type(v[0]) is int:
355 size = len(buf) - off
356 else:
357 size = res[v[2]]
Ole Troana03f4ef2016-12-02 12:53:55 +0100358 res.append(buf[off:off + size])
359 else:
Ole Troanf5984bd2016-12-18 13:15:08 +0100360 e = v[0] if type(v[0]) is int else res[v[2]]
361 if e == -1:
362 e = (len(buf) - off) / v[1].size
Ole Troana03f4ef2016-12-02 12:53:55 +0100363 lst = []
364 res.append(lst)
365 size = 0
366 for i in range(e):
367 lst.append(v[1].unpack_from(buf, off + size)[0])
368 size += v[1].size
369 else:
370 if callable(v):
Ole Troanca3b6f12017-10-31 14:50:13 +0100371 size = 0
Ole Troan4df97162017-07-07 16:06:08 +0200372 (s, l) = v(self, False, buf, off, None)
Ole Troana03f4ef2016-12-02 12:53:55 +0100373 res.append(l)
374 size += s
375 else:
376 res.append(v.unpack_from(buf, off)[0])
377 size = v.size
378
379 return off + size - offset, msgdef['return_tuple']._make(res)
380
381 def ret_tup(self, name):
382 if name in self.messages and 'return_tuple' in self.messages[name]:
383 return self.messages[name]['return_tuple']
384 return None
385
Ole Troan4df97162017-07-07 16:06:08 +0200386 def add_message(self, name, msgdef, typeonly=False):
Ole Troana03f4ef2016-12-02 12:53:55 +0100387 if name in self.messages:
388 raise ValueError('Duplicate message name: ' + name)
389
390 args = collections.OrderedDict()
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100391 argtypes = collections.OrderedDict()
Ole Troana03f4ef2016-12-02 12:53:55 +0100392 fields = []
393 msg = {}
Ole Troan895b6e82017-10-20 13:28:20 +0200394 total_size = 0
395 sizes = {}
Ole Troanf5984bd2016-12-18 13:15:08 +0100396 for i, f in enumerate(msgdef):
Ole Troana03f4ef2016-12-02 12:53:55 +0100397 if type(f) is dict and 'crc' in f:
398 msg['crc'] = f['crc']
399 continue
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100400 field_type = f[0]
Ole Troana03f4ef2016-12-02 12:53:55 +0100401 field_name = f[1]
Ole Troanf5984bd2016-12-18 13:15:08 +0100402 if len(f) == 3 and f[2] == 0 and i != len(msgdef) - 2:
403 raise ValueError('Variable Length Array must be last: ' + name)
Ole Troan895b6e82017-10-20 13:28:20 +0200404 size, s = self.__struct(*f)
405 args[field_name] = s
406 if type(s) == list and type(s[0]) == int and type(s[1]) == struct.Struct:
407 if s[0] < 0:
408 sizes[field_name] = size
409 else:
410 sizes[field_name] = size
411 total_size += s[0] * size
412 else:
413 sizes[field_name] = size
414 total_size += size
415
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100416 argtypes[field_name] = field_type
Ole Troan4df97162017-07-07 16:06:08 +0200417 if len(f) == 4: # Find offset to # elements field
418 idx = list(args.keys()).index(f[3]) - i
419 args[field_name].append(idx)
Ole Troana03f4ef2016-12-02 12:53:55 +0100420 fields.append(field_name)
421 msg['return_tuple'] = collections.namedtuple(name, fields,
Ole Troan4df97162017-07-07 16:06:08 +0200422 rename=True)
Ole Troana03f4ef2016-12-02 12:53:55 +0100423 self.messages[name] = msg
424 self.messages[name]['args'] = args
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100425 self.messages[name]['argtypes'] = argtypes
Ole Troan3cc49712017-03-08 12:02:24 +0100426 self.messages[name]['typeonly'] = typeonly
Ole Troan895b6e82017-10-20 13:28:20 +0200427 self.messages[name]['sizes'] = [total_size, sizes]
Ole Troana03f4ef2016-12-02 12:53:55 +0100428 return self.messages[name]
429
430 def add_type(self, name, typedef):
Ole Troan4df97162017-07-07 16:06:08 +0200431 return self.add_message('vl_api_' + name + '_t', typedef,
432 typeonly=True)
Ole Troana03f4ef2016-12-02 12:53:55 +0100433
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100434 def make_function(self, name, i, msgdef, multipart, async):
Wojciech Dec64bc6122016-12-12 11:32:25 +0100435 if (async):
Ole Troan5016f992017-01-19 09:44:44 +0100436 f = lambda **kwargs: (self._call_vpp_async(i, msgdef, **kwargs))
Wojciech Dec64bc6122016-12-12 11:32:25 +0100437 else:
Ole Troan4df97162017-07-07 16:06:08 +0200438 f = lambda **kwargs: (self._call_vpp(i, msgdef, multipart,
439 **kwargs))
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100440 args = self.messages[name]['args']
441 argtypes = self.messages[name]['argtypes']
442 f.__name__ = str(name)
Ole Troan4df97162017-07-07 16:06:08 +0200443 f.__doc__ = ", ".join(["%s %s" %
444 (argtypes[k], k) for k in args.keys()])
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100445 return f
Ole Troana03f4ef2016-12-02 12:53:55 +0100446
Klement Sekera7112c542017-03-01 09:53:19 +0100447 @property
448 def api(self):
449 if not hasattr(self, "_api"):
450 raise Exception("Not connected, api definitions not available")
451 return self._api
452
Wojciech Dec64bc6122016-12-12 11:32:25 +0100453 def _register_functions(self, async=False):
Ole Troana03f4ef2016-12-02 12:53:55 +0100454 self.id_names = [None] * (self.vpp_dictionary_maxid + 1)
455 self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1)
Klement Sekera7112c542017-03-01 09:53:19 +0100456 self._api = Empty()
Ole Troan4df97162017-07-07 16:06:08 +0200457 for name, msgdef in vpp_iterator(self.messages):
458 if self.messages[name]['typeonly']:
459 continue
Ole Troan3cc49712017-03-08 12:02:24 +0100460 crc = self.messages[name]['crc']
461 n = name + '_' + crc[2:]
Ole Troan4df97162017-07-07 16:06:08 +0200462 i = vpp_api.vac_get_msg_index(n.encode())
Ole Troan3cc49712017-03-08 12:02:24 +0100463 if i > 0:
Ole Troana03f4ef2016-12-02 12:53:55 +0100464 self.id_msgdef[i] = msgdef
465 self.id_names[i] = name
466 multipart = True if name.find('_dump') > 0 else False
Klement Sekera7112c542017-03-01 09:53:19 +0100467 f = self.make_function(name, i, msgdef, multipart, async)
468 setattr(self._api, name, FuncWrapper(f))
469
Ole Troandfc9b7c2017-03-06 23:51:57 +0100470 # old API stuff starts here - will be removed in 17.07
Klement Sekera7112c542017-03-01 09:53:19 +0100471 if hasattr(self, name):
472 raise NameError(
473 3, "Conflicting name in JSON definition: `%s'" % name)
474 setattr(self, name, f)
475 # old API stuff ends here
Ole Troan3cc49712017-03-08 12:02:24 +0100476 else:
Ole Troan4df97162017-07-07 16:06:08 +0200477 self.logger.debug(
478 'No such message type or failed CRC checksum: %s', n)
Ole Troana03f4ef2016-12-02 12:53:55 +0100479
Ole Troana74b7412017-11-01 10:49:03 +0100480 def _write_new_cffi(self, buf):
Ole Troan5016f992017-01-19 09:44:44 +0100481 """Send a binary-packed message to VPP."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100482 if not self.connected:
483 raise IOError(1, 'Not connected')
Ole Troan4df97162017-07-07 16:06:08 +0200484 return vpp_api.vac_write(ffi.from_buffer(buf), len(buf))
Ole Troana03f4ef2016-12-02 12:53:55 +0100485
Ole Troana74b7412017-11-01 10:49:03 +0100486 def _write_legacy_cffi(self, buf):
487 """Send a binary-packed message to VPP."""
488 if not self.connected:
489 raise IOError(1, 'Not connected')
490 return vpp_api.vac_write(str(buf), len(buf))
491
Ole Troan4df97162017-07-07 16:06:08 +0200492 def _read(self):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100493 if not self.connected:
494 raise IOError(1, 'Not connected')
Ole Troan3cc49712017-03-08 12:02:24 +0100495 mem = ffi.new("char **")
496 size = ffi.new("int *")
Damjan Marion5fec1e82017-04-13 19:13:47 +0200497 rv = vpp_api.vac_read(mem, size, self.read_timeout)
Ole Troan3cc49712017-03-08 12:02:24 +0100498 if rv:
Ole Troanb0856b42017-08-17 12:48:08 +0200499 raise IOError(rv, 'vac_read failed')
Ole Troan3cc49712017-03-08 12:02:24 +0100500 msg = bytes(ffi.buffer(mem[0], size[0]))
Damjan Marion5fec1e82017-04-13 19:13:47 +0200501 vpp_api.vac_free(mem[0])
Ole Troan3cc49712017-03-08 12:02:24 +0100502 return msg
Ole Troana03f4ef2016-12-02 12:53:55 +0100503
Ole Troan4df97162017-07-07 16:06:08 +0200504 def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen,
505 async):
Ole Troan6bf177c2017-08-17 10:34:32 +0200506 pfx = chroot_prefix.encode() if chroot_prefix else ffi.NULL
507 rv = vpp_api.vac_connect(name.encode(), pfx, msg_handler, rx_qlen)
Ole Troana03f4ef2016-12-02 12:53:55 +0100508 if rv != 0:
509 raise IOError(2, 'Connect failed')
Ole Troan7e3a8752016-12-05 10:27:09 +0100510 self.connected = True
Ole Troana03f4ef2016-12-02 12:53:55 +0100511
Damjan Marion5fec1e82017-04-13 19:13:47 +0200512 self.vpp_dictionary_maxid = vpp_api.vac_msg_table_max_index()
Wojciech Dec64bc6122016-12-12 11:32:25 +0100513 self._register_functions(async=async)
Ole Troana03f4ef2016-12-02 12:53:55 +0100514
515 # Initialise control ping
Ole Troan3cc49712017-03-08 12:02:24 +0100516 crc = self.messages['control_ping']['crc']
Ole Troan4df97162017-07-07 16:06:08 +0200517 self.control_ping_index = vpp_api.vac_get_msg_index(
518 ('control_ping' + '_' + crc[2:]).encode())
Ole Troana03f4ef2016-12-02 12:53:55 +0100519 self.control_ping_msgdef = self.messages['control_ping']
Ole Troan4df97162017-07-07 16:06:08 +0200520 return rv
Ole Troana03f4ef2016-12-02 12:53:55 +0100521
Ole Troan6bf177c2017-08-17 10:34:32 +0200522 def connect(self, name, chroot_prefix=None, async=False, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100523 """Attach to VPP.
524
525 name - the name of the client.
526 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
527 async - if true, messages are sent without waiting for a reply
528 rx_qlen - the length of the VPP message receive queue between
529 client and server.
530 """
Ole Troan4df97162017-07-07 16:06:08 +0200531 msg_handler = vac_callback_sync if not async else vac_callback_async
Ole Troandfc9b7c2017-03-06 23:51:57 +0100532 return self.connect_internal(name, msg_handler, chroot_prefix, rx_qlen,
533 async)
534
Ole Troan6bf177c2017-08-17 10:34:32 +0200535 def connect_sync(self, name, chroot_prefix=None, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100536 """Attach to VPP in synchronous mode. Application must poll for events.
537
538 name - the name of the client.
539 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
540 rx_qlen - the length of the VPP message receive queue between
541 client and server.
542 """
543
Ole Troan3cc49712017-03-08 12:02:24 +0100544 return self.connect_internal(name, ffi.NULL, chroot_prefix, rx_qlen,
Ole Troandfc9b7c2017-03-06 23:51:57 +0100545 async=False)
546
Ole Troana03f4ef2016-12-02 12:53:55 +0100547 def disconnect(self):
Ole Troan5016f992017-01-19 09:44:44 +0100548 """Detach from VPP."""
Damjan Marion5fec1e82017-04-13 19:13:47 +0200549 rv = vpp_api.vac_disconnect()
Ole Troan5016f992017-01-19 09:44:44 +0100550 self.connected = False
Ole Troana03f4ef2016-12-02 12:53:55 +0100551 return rv
552
Ole Troan5016f992017-01-19 09:44:44 +0100553 def msg_handler_sync(self, msg):
554 """Process an incoming message from VPP in sync mode.
555
556 The message may be a reply or it may be an async notification.
557 """
558 r = self.decode_incoming_msg(msg)
559 if r is None:
Ole Troana03f4ef2016-12-02 12:53:55 +0100560 return
561
Ole Troan5016f992017-01-19 09:44:44 +0100562 # If we have a context, then use the context to find any
563 # request waiting for a reply
564 context = 0
565 if hasattr(r, 'context') and r.context > 0:
566 context = r.context
Ole Troan5f9dcff2016-08-01 04:59:13 +0200567
Ole Troana03f4ef2016-12-02 12:53:55 +0100568 msgname = type(r).__name__
Ole Troan57c3d662016-09-12 22:00:32 +0200569
Ole Troan5016f992017-01-19 09:44:44 +0100570 if context == 0:
571 # No context -> async notification that we feed to the callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100572 self.message_queue.put_nowait(r)
Ole Troana03f4ef2016-12-02 12:53:55 +0100573 else:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100574 raise IOError(2, 'RPC reply message received in event handler')
Ole Troan5016f992017-01-19 09:44:44 +0100575
576 def decode_incoming_msg(self, msg):
577 if not msg:
Ole Troan3cc49712017-03-08 12:02:24 +0100578 self.logger.warning('vpp_api.read failed')
Ole Troan5016f992017-01-19 09:44:44 +0100579 return
580
581 i, ci = self.header.unpack_from(msg, 0)
582 if self.id_names[i] == 'rx_thread_exit':
583 return
584
585 #
586 # Decode message and returns a tuple.
587 #
588 msgdef = self.id_msgdef[i]
589 if not msgdef:
590 raise IOError(2, 'Reply message undefined')
591
592 r = self.decode(msgdef, msg)
593
Ole Troana03f4ef2016-12-02 12:53:55 +0100594 return r
595
Ole Troan5016f992017-01-19 09:44:44 +0100596 def msg_handler_async(self, msg):
597 """Process a message from VPP in async mode.
598
599 In async mode, all messages are returned to the callback.
600 """
601 r = self.decode_incoming_msg(msg)
602 if r is None:
603 return
604
605 msgname = type(r).__name__
606
Ole Troan4df97162017-07-07 16:06:08 +0200607 if self.event_callback:
608 self.event_callback(msgname, r)
Ole Troan5016f992017-01-19 09:44:44 +0100609
610 def _control_ping(self, context):
611 """Send a ping command."""
612 self._call_vpp_async(self.control_ping_index,
Ole Troan4df97162017-07-07 16:06:08 +0200613 self.control_ping_msgdef,
Ole Troan5016f992017-01-19 09:44:44 +0100614 context=context)
615
616 def _call_vpp(self, i, msgdef, multipart, **kwargs):
617 """Given a message, send the message and await a reply.
618
619 msgdef - the message packing definition
620 i - the message type index
621 multipart - True if the message returns multiple
622 messages in return.
623 context - context number - chosen at random if not
624 supplied.
625 The remainder of the kwargs are the arguments to the API call.
626
627 The return value is the message or message array containing
628 the response. It will raise an IOError exception if there was
629 no response within the timeout window.
630 """
631
Ole Troan4df97162017-07-07 16:06:08 +0200632 if 'context' not in kwargs:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100633 context = self.get_context()
634 kwargs['context'] = context
635 else:
636 context = kwargs['context']
637 kwargs['_vl_msg_id'] = i
638 b = self.encode(msgdef, kwargs)
Ole Troan5016f992017-01-19 09:44:44 +0100639
Damjan Marion5fec1e82017-04-13 19:13:47 +0200640 vpp_api.vac_rx_suspend()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100641 self._write(b)
Ole Troan5016f992017-01-19 09:44:44 +0100642
643 if multipart:
644 # Send a ping after the request - we use its response
645 # to detect that we have seen all results.
646 self._control_ping(context)
647
648 # Block until we get a reply.
Ole Troandfc9b7c2017-03-06 23:51:57 +0100649 rl = []
650 while (True):
651 msg = self._read()
652 if not msg:
Ole Troan4df97162017-07-07 16:06:08 +0200653 raise IOError(2, 'VPP API client: read failed')
Ole Troan5016f992017-01-19 09:44:44 +0100654
Ole Troandfc9b7c2017-03-06 23:51:57 +0100655 r = self.decode_incoming_msg(msg)
656 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200657 if context not in r or r.context == 0 or context != r.context:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100658 self.message_queue.put_nowait(r)
659 continue
660
661 if not multipart:
662 rl = r
663 break
664 if msgname == 'control_ping_reply':
665 break
666
667 rl.append(r)
668
Damjan Marion5fec1e82017-04-13 19:13:47 +0200669 vpp_api.vac_rx_resume()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100670
671 return rl
Ole Troan5016f992017-01-19 09:44:44 +0100672
673 def _call_vpp_async(self, i, msgdef, **kwargs):
674 """Given a message, send the message and await a reply.
675
676 msgdef - the message packing definition
677 i - the message type index
678 context - context number - chosen at random if not
679 supplied.
680 The remainder of the kwargs are the arguments to the API call.
681 """
Ole Troan4df97162017-07-07 16:06:08 +0200682 if 'context' not in kwargs:
Ole Troan7e3a8752016-12-05 10:27:09 +0100683 context = self.get_context()
684 kwargs['context'] = context
685 else:
686 context = kwargs['context']
687 kwargs['_vl_msg_id'] = i
688 b = self.encode(msgdef, kwargs)
689
690 self._write(b)
691
Ole Troana03f4ef2016-12-02 12:53:55 +0100692 def register_event_callback(self, callback):
Ole Troan5016f992017-01-19 09:44:44 +0100693 """Register a callback for async messages.
Ole Troana03f4ef2016-12-02 12:53:55 +0100694
Ole Troan5016f992017-01-19 09:44:44 +0100695 This will be called for async notifications in sync mode,
696 and all messages in async mode. In sync mode, replies to
697 requests will not come here.
698
699 callback is a fn(msg_type_name, msg_type) that will be
700 called when a message comes in. While this function is
701 executing, note that (a) you are in a background thread and
702 may wish to use threading.Lock to protect your datastructures,
703 and (b) message processing from VPP will stop (so if you take
704 a long while about it you may provoke reply timeouts or cause
705 VPP to fill the RX buffer). Passing None will disable the
706 callback.
707 """
708 self.event_callback = callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100709
710 def thread_msg_handler(self):
711 """Python thread calling the user registerd message handler.
712
713 This is to emulate the old style event callback scheme. Modern
714 clients should provide their own thread to poll the event
715 queue.
716 """
717 while True:
718 r = self.message_queue.get()
719 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200720 if self.event_callback:
721 self.event_callback(msgname, r)