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:
8 # http://www.apache.org/licenses/LICENSE-2.0
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.
17 import argparse, sys, os, importlib, pprint
19 parser = argparse.ArgumentParser(description='VPP Python API generator')
20 parser.add_argument('-i', action="store", dest="inputfile")
21 parser.add_argument('-c', '--cfile', action="store")
22 args = parser.parse_args()
26 inputfile = args.inputfile.replace('.py', '')
27 cfg = importlib.import_module(inputfile, package=None)
29 # https://docs.python.org/3/library/struct.html
30 format_struct = {'u8': 'B',
36 'vl_api_fib_path_t' : 'IIBBBBBBBBBBBBBBBBBBBBB',
37 'vl_api_ip4_fib_counter_t' : 'IBQQ',
38 'vl_api_ip6_fib_counter_t' : 'QQBQQ',
39 'vl_api_lisp_adjacency_t' : 'B' * 35,
42 # NB: If new types are introduced in vpe.api, these must be updated.
50 'vl_api_fib_path_t' : 29,
51 'vl_api_ip4_fib_counter_t' : 21,
52 'vl_api_ip6_fib_counter_t' : 33,
53 'vl_api_lisp_adjacency_t' : 35,
60 argslist.append(i[1][1:])
74 if len(i) is 3 or len(i) is 4: # TODO: add support for variable length arrays (VPP-162)
75 size = type_size[i[0]]
76 bytecount += size * int(i[2])
77 # Check if we have a zero length array
79 tup += 'msg[' + str(bytecount) + ':],'
85 tup += 'tr[' + str(j) + '],'
87 pack += format_struct[i[0]] * int(i[2])
88 tup += 'tr[' + str(j) + ':' + str(j + int(i[2])) + '],'
91 bytecount += type_size[i[0]]
92 pack += format_struct[i[0]]
93 tup += 'tr[' + str(j) + '],'
94 return pack, bytecount, tup, zeroarray
96 def get_reply_func(f):
97 if f['name']+'_reply' in func_name:
98 return func_name[f['name']+'_reply']
99 if f['name'].find('_dump') > 0:
100 r = f['name'].replace('_dump','_details')
106 # Read enums from stdin
111 l = l.replace(',\n','')
114 l = l.replace('VL_API_','').lower()
116 enums_by_index[i] = l
119 return enums_by_name, enums_by_index
121 def get_definitions():
126 for a in cfg.messages:
127 pack, packlen, tup, zeroarray = get_pack(a[1:])
128 func_name[a[0]] = dict([('name', a[0]), ('pack', pack), ('packlen', packlen), ('tup', tup), ('args', get_args(a[1:])),
129 ('zeroarray', zeroarray)])
130 func_list.append(func_name[a[0]]) # Indexed by name
131 return func_list, func_name
133 def generate_c_macros(func_list, enums_by_name):
134 file = open(args.cfile, 'w+')
135 print >>file, "#define foreach_api_msg \\"
137 if not f['name'] in enums_by_name:
139 print >>file, "_(" + f['name'].upper() + ", " + f['name'] + ") \\"
141 void pneum_set_handlers(void) {
143 api_func_table[VL_API_##N] = sizeof(vl_api_##n##_t);
150 # Print array with a hash of 'decode' and 'multipart'
151 # Simplify to do only decode for now. And deduce multipart from _dump?
153 def decode_function_print(name, args, pack, packlen, tup):
155 print(u'def ' + name + u'_decode(msg):')
156 print(u" n = namedtuple('" + name + "', '" + ', '.join(args) + "')" +
161 print(u" tr = unpack('" + pack + "', msg[:" + str(packlen) + "])")
162 print(u" r = n._make((" + tup + "))" +
169 def function_print(name, id, args, pack, multipart, zeroarray):
171 print "def", name + "(async = False):"
173 print "def", name + "(" + ', '.join(args[3:]) + ", async = False):"
174 print " global waiting_for_reply"
175 print " context = get_context(" + id + ")"
178 results[context] = {}
179 results[context]['e'] = threading.Event()
180 results[context]['e'].clear()
181 results[context]['r'] = []
182 waiting_for_reply = True
184 if multipart == True:
185 print " results[context]['m'] = True"
187 if zeroarray == True:
188 print " vpp_api.write(pack('" + pack + "', " + id + ", 0, context, " + ', '.join(args[3:-1]) + ") + " + args[-1] + ")"
190 print " vpp_api.write(pack('" + pack + "', " + id + ", 0, context, " + ', '.join(args[3:]) + "))"
192 if multipart == True:
193 print " vpp_api.write(pack('>HII', VL_API_CONTROL_PING, 0, context))"
197 results[context]['e'].wait(5)
198 return results[context]['r']
203 # Should dynamically create size
205 def api_table_print (name, msg_id):
207 print('api_func_table[' + msg_id + '] = ' + f)
210 # Generate the main Python file
216 # AUTO-GENERATED FILE. PLEASE DO NOT EDIT.
218 import sys, time, threading, signal, os, logging
220 from collections import namedtuple
223 # Import C API shared object
229 waiting_for_reply = False
232 # XXX: Make this return a unique number
239 def msg_handler(msg):
240 global result, context, event_callback, waiting_for_reply
242 logging.warning('vpp_api.read failed')
245 id = unpack('>H', msg[0:2])
246 logging.debug('Received message', id[0])
247 if id[0] == VL_API_RX_THREAD_EXIT:
248 logging.info("We got told to leave")
252 # Decode message and returns a tuple.
254 logging.debug('api_func', api_func_table[id[0]])
255 r = api_func_table[id[0]](msg)
257 logging.warning('Message decode failed', id[0])
260 if 'context' in r._asdict():
265 # XXX: Call provided callback for event
266 # Are we guaranteed to not get an event during processing of other messages?
267 # How to differentiate what's a callback message and what not? Context = 0?
269 logging.debug('R:', context, r, waiting_for_reply)
270 if waiting_for_reply == False:
275 # Collect results until control ping
277 if id[0] == VL_API_CONTROL_PING_REPLY:
278 results[context]['e'].set()
279 waiting_for_reply = False
281 if not context in results:
282 logging.warning('Not expecting results for this context', context)
284 if 'm' in results[context]:
285 results[context]['r'].append(r)
288 results[context]['r'] = r
289 results[context]['e'].set()
290 waiting_for_reply = False
293 signal.alarm(3) # 3 second
294 rv = vpp_api.connect(name, msg_handler)
296 logging.info("Connect:", rv)
300 rv = vpp_api.disconnect()
301 logging.info("Disconnected")
304 def register_event_callback(callback):
305 global event_callback
306 event_callback = callback
309 enums_by_name, enums_by_index = get_enums()
310 func_list, func_name = get_definitions()
313 # Not needed with the new msg_size field.
314 # generate_c_macros(func_list, enums_by_name)
317 pp = pprint.PrettyPrinter(indent=4)
318 #print 'enums_by_index =', pp.pprint(enums_by_index)
319 #print 'func_name =', pp.pprint(func_name)
324 # 1) The VPE API lacks a clear definition of what messages are reply messages
325 # 2) Length is missing, and has to be pre-known or in case of variable sized ones calculated per message type
328 #if f['name'].find('_reply') > 0 or f['name'].find('_details') > 0:
329 decode_function_print(f['name'], f['args'], f['pack'], f['packlen'], f['tup'])
331 #r = get_reply_func(f)
334 # # XXX: Functions here are not taken care of. E.g. events
336 # print('Missing function', f)
339 if f['name'].find('_dump') > 0:
340 f['multipart'] = True
342 f['multipart'] = False
343 msg_id_in = 'VL_API_' + f['name'].upper()
344 function_print(f['name'], msg_id_in, f['args'], f['pack'], f['multipart'], f['zeroarray'])
347 print "api_func_table = [0] * 10000"
349 # if f['name'].find('_reply') > 0 or f['name'].find('_details') > 0:
350 msg_id_in = 'VL_API_' + f['name'].upper()
351 api_table_print(f['name'], msg_id_in)