blob: 94cc829f64ee8d496866ca36b0034713a5af6ffa [file] [log] [blame]
Ole Troan6855f6c2016-04-09 03:16:30 +02001#!/usr/bin/env python
2#
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.
15#
16
17import argparse, sys, os, importlib, pprint
18
19parser = argparse.ArgumentParser(description='VPP Python API generator')
20parser.add_argument('-i', action="store", dest="inputfile")
21parser.add_argument('-c', '--cfile', action="store")
22args = parser.parse_args()
23
24sys.path.append(".")
25
26inputfile = args.inputfile.replace('.py', '')
27cfg = importlib.import_module(inputfile, package=None)
28
29# https://docs.python.org/3/library/struct.html
30format_struct = {'u8': 'B',
31 'u16' : 'H',
32 'u32' : 'I',
33 'i32' : 'i',
34 'u64' : 'Q',
35 'f64' : 'd',
36 'vl_api_ip4_fib_counter_t' : 'IBQQ',
37 'vl_api_ip6_fib_counter_t' : 'QQBQQ',
38 };
Ole Troanfc6cf282016-04-25 12:36:02 +020039#
40# NB: If new types are introduced in vpe.api, these must be updated.
41#
Ole Troan6855f6c2016-04-09 03:16:30 +020042type_size = {'u8': 1,
43 'u16' : 2,
44 'u32' : 4,
45 'i32' : 4,
46 'u64' : 8,
47 'f64' : 8,
48 'vl_api_ip4_fib_counter_t' : 21,
49 'vl_api_ip6_fib_counter_t' : 33,
50};
51
52def get_args(t):
Ole Troanfc6cf282016-04-25 12:36:02 +020053 argslist = []
Ole Troan6855f6c2016-04-09 03:16:30 +020054 for i in t:
Ole Troanfc6cf282016-04-25 12:36:02 +020055 argslist.append(i[1].replace('_',''))
56 return argslist
Ole Troan6855f6c2016-04-09 03:16:30 +020057
58def get_pack(t):
Ole Troanfc6cf282016-04-25 12:36:02 +020059 zeroarray = False
Ole Troan6855f6c2016-04-09 03:16:30 +020060 bytecount = 0
61 pack = '>'
62 tup = u''
63 j = -1
64 for i in t:
65 j += 1
66 if len(i) is 3:
67 size = type_size[i[0]]
68 bytecount += size * int(i[2])
Ole Troanfc6cf282016-04-25 12:36:02 +020069 # Check if we have a zero length array
Ole Troan6855f6c2016-04-09 03:16:30 +020070 if i[2] == '0':
71 tup += 'msg[' + str(bytecount) + ':],'
Ole Troanfc6cf282016-04-25 12:36:02 +020072 zeroarray = True
Ole Troan6855f6c2016-04-09 03:16:30 +020073 continue
74 if size == 1:
75 n = i[2] * size
76 pack += str(n) + 's'
77 tup += 'tr[' + str(j) + '],'
78 continue
79 pack += format_struct[i[0]] * int(i[2])
80 tup += 'tr[' + str(j) + ':' + str(j + int(i[2])) + '],'
81 j += int(i[2]) - 1
82 else:
83 bytecount += type_size[i[0]]
84 pack += format_struct[i[0]]
85 tup += 'tr[' + str(j) + '],'
Ole Troanfc6cf282016-04-25 12:36:02 +020086 return pack, bytecount, tup, zeroarray
Ole Troan6855f6c2016-04-09 03:16:30 +020087
88def get_reply_func(f):
89 if f['name']+'_reply' in func_name:
90 return func_name[f['name']+'_reply']
91 if f['name'].find('_dump') > 0:
92 r = f['name'].replace('_dump','_details')
93 if r in func_name:
94 return func_name[r]
95 return None
96
97def get_enums():
98 # Read enums from stdin
99 enums_by_name = {}
100 enums_by_index = {}
101 i = 1
102 for l in sys.stdin:
103 l = l.replace(',\n','')
104 print l, "=", i
105
106 l = l.replace('VL_API_','').lower()
107 enums_by_name[l] = i
108 enums_by_index[i] = l
109
110 i += 1
111 return enums_by_name, enums_by_index
112
113def get_definitions():
114 # Pass 1
115 func_list = []
116 func_name = {}
117 i = 1
118 for a in cfg.vppapidef:
Ole Troanfc6cf282016-04-25 12:36:02 +0200119 pack, packlen, tup, zeroarray = get_pack(a[1:])
120 func_name[a[0]] = dict([('name', a[0]), ('pack', pack), ('packlen', packlen), ('tup', tup), ('args', get_args(a[1:])),
121 ('zeroarray', zeroarray)])
Ole Troan6855f6c2016-04-09 03:16:30 +0200122 func_list.append(func_name[a[0]]) # Indexed by name
123 return func_list, func_name
124
125def generate_c_macros(func_list, enums_by_name):
126 file = open(args.cfile, 'w+')
127 print >>file, "#define foreach_api_msg \\"
128 for f in func_list:
129 if not f['name'] in enums_by_name:
130 continue
131 print >>file, "_(" + f['name'].upper() + ", " + f['name'] + ") \\"
132 print >>file, '''
133void pneum_set_handlers(void) {
134#define _(N,n) \\
135 api_func_table[VL_API_##N] = sizeof(vl_api_##n##_t);
136 foreach_api_msg;
137#undef _
138}
139 '''
140
141#
Ole Troan6855f6c2016-04-09 03:16:30 +0200142# Print array with a hash of 'decode' and 'multipart'
143# Simplify to do only decode for now. And deduce multipart from _dump?
144#
145def decode_function_print(name, args, pack, packlen, tup):
146
147 print(u'def ' + name + u'_decode(msg):')
Ole Troanfc6cf282016-04-25 12:36:02 +0200148 print(u" n = namedtuple('" + name + "', '" + ', '.join(args) + "')" +
Ole Troan6855f6c2016-04-09 03:16:30 +0200149 '''
150 if not n:
151 return None
152 ''')
153 print(u" tr = unpack('" + pack + "', msg[:" + str(packlen) + "])")
154 print(u" r = n._make((" + tup + "))" +
155 '''
156 if not r:
157 return None
158 return r
159 ''')
160
Ole Troanfc6cf282016-04-25 12:36:02 +0200161def function_print(name, id, args, pack, multipart, zeroarray):
162 if len(args) < 4:
Ole Troan6855f6c2016-04-09 03:16:30 +0200163 print "def", name + "(async = False):"
164 else:
Ole Troanfc6cf282016-04-25 12:36:02 +0200165 print "def", name + "(" + ', '.join(args[3:]) + ", async = False):"
Ole Troan6855f6c2016-04-09 03:16:30 +0200166 print " global waiting_for_reply"
167 print " context = get_context(" + id + ")"
168
169 print '''
170 results[context] = {}
171 results[context]['e'] = threading.Event()
172 results[context]['e'].clear()
173 results[context]['r'] = []
174 waiting_for_reply = True
175 '''
176 if multipart == True:
177 print " results[context]['m'] = True"
178
Ole Troanfc6cf282016-04-25 12:36:02 +0200179 if zeroarray == True:
180 print " vpp_api.write(pack('" + pack + "', " + id + ", 0, context, " + ', '.join(args[3:-1]) + ") + " + args[-1] + ")"
181 else:
182 print " vpp_api.write(pack('" + pack + "', " + id + ", 0, context, " + ', '.join(args[3:]) + "))"
Ole Troan6855f6c2016-04-09 03:16:30 +0200183
184 if multipart == True:
185 print " vpp_api.write(pack('>HII', VL_API_CONTROL_PING, 0, context))"
186
187 print '''
188 if not async:
189 results[context]['e'].wait(5)
190 return results[context]['r']
191 return context
192 '''
193
194#
195# Should dynamically create size
196#
197def api_table_print (name, msg_id):
198 f = name + '_decode'
199 print('api_func_table[' + msg_id + '] = ' + f)
200
201#
202# Generate the main Python file
203#
204
205print '''#!/usr/bin/env python3
206
Ole Troanfc6cf282016-04-25 12:36:02 +0200207#
208# AUTO-GENERATED FILE. PLEASE DO NOT EDIT.
209#
Ole Troan6855f6c2016-04-09 03:16:30 +0200210import sys, time, threading, signal, os, logging
211from struct import *
212from collections import namedtuple
213
214#
215# Import C API shared object
216#
217import vpp_api
218
219context = 0
220results = {}
221waiting_for_reply = False
222
223#
224# XXX: Make this return a unique number
225#
226def get_context(id):
227 global context
228 context += 1
229 return context
230
231def msg_handler(msg):
232 global result, context, event_callback, waiting_for_reply
233 if not msg:
234 logging.warning('vpp_api.read failed')
235 return
236
237 id = unpack('>H', msg[0:2])
238 logging.debug('Received message', id[0])
239 if id[0] == VL_API_RX_THREAD_EXIT:
240 logging.info("We got told to leave")
241 return;
242
243 #
244 # Decode message and returns a tuple.
245 #
246 logging.debug('api_func', api_func_table[id[0]])
247 r = api_func_table[id[0]](msg)
248 if not r:
249 logging.warning('Message decode failed', id[0])
250 return
251
252 if 'context' in r._asdict():
253 if r.context > 0:
254 context = r.context
255
256 #
257 # XXX: Call provided callback for event
258 # Are we guaranteed to not get an event during processing of other messages?
259 # How to differentiate what's a callback message and what not? Context = 0?
260 #
261 logging.debug('R:', context, r, waiting_for_reply)
262 if waiting_for_reply == False:
263 event_callback(r)
264 return
265
266 #
267 # Collect results until control ping
268 #
269 if id[0] == VL_API_CONTROL_PING_REPLY:
270 results[context]['e'].set()
271 waiting_for_reply = False
272 return
273 if not context in results:
274 logging.warning('Not expecting results for this context', context)
275 return
276 if 'm' in results[context]:
277 results[context]['r'].append(r)
278 return
279
280 results[context]['r'] = r
281 results[context]['e'].set()
282 waiting_for_reply = False
283
284def connect(name):
285 signal.alarm(3) # 3 second
286 rv = vpp_api.connect(name, msg_handler)
287 signal.alarm(0)
288 logging.info("Connect:", rv)
289 return rv
290
291def disconnect():
292 rv = vpp_api.disconnect()
293 logging.info("Disconnected")
294 return rv
295
296def register_event_callback(callback):
297 global event_callback
298 event_callback = callback
299'''
300
301enums_by_name, enums_by_index = get_enums()
302func_list, func_name = get_definitions()
303
304#
305# Not needed with the new msg_size field.
306# generate_c_macros(func_list, enums_by_name)
307#
308
309pp = pprint.PrettyPrinter(indent=4)
310#print 'enums_by_index =', pp.pprint(enums_by_index)
311#print 'func_name =', pp.pprint(func_name)
312
313# Pass 2
314
315#
316# 1) The VPE API lacks a clear definition of what messages are reply messages
317# 2) Length is missing, and has to be pre-known or in case of variable sized ones calculated per message type
318#
319for f in func_list:
320 #if f['name'].find('_reply') > 0 or f['name'].find('_details') > 0:
Ole Troanfc6cf282016-04-25 12:36:02 +0200321 decode_function_print(f['name'], f['args'], f['pack'], f['packlen'], f['tup'])
Ole Troan6855f6c2016-04-09 03:16:30 +0200322
323 #r = get_reply_func(f)
324 #if not r:
325 # #
326 # # XXX: Functions here are not taken care of. E.g. events
327 # #
328 # print('Missing function', f)
329 # continue
330
331 if f['name'].find('_dump') > 0:
332 f['multipart'] = True
333 else:
334 f['multipart'] = False
335 msg_id_in = 'VL_API_' + f['name'].upper()
Ole Troanfc6cf282016-04-25 12:36:02 +0200336 function_print(f['name'], msg_id_in, f['args'], f['pack'], f['multipart'], f['zeroarray'])
Ole Troan6855f6c2016-04-09 03:16:30 +0200337
338
339print "api_func_table = [0] * 10000"
340for f in func_list:
341 # if f['name'].find('_reply') > 0 or f['name'].find('_details') > 0:
342 msg_id_in = 'VL_API_' + f['name'].upper()
343 api_table_print(f['name'], msg_id_in)