blob: bbc9c3290aca44add1455910d3cc3e8bf9816382 [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 };
39type_size = {'u8': 1,
40 'u16' : 2,
41 'u32' : 4,
42 'i32' : 4,
43 'u64' : 8,
44 'f64' : 8,
45 'vl_api_ip4_fib_counter_t' : 21,
46 'vl_api_ip6_fib_counter_t' : 33,
47};
48
49def get_args(t):
50 args = None
51 for i in t:
52 arg = i[1]
53 arg = arg.replace('_','')
54 if args == None:
55 args = arg
56 continue
57 args = args + ', ' + arg
58 return args
59
60def get_pack(t):
61 bytecount = 0
62 pack = '>'
63 tup = u''
64 j = -1
65 for i in t:
66 j += 1
67 if len(i) is 3:
68 size = type_size[i[0]]
69 bytecount += size * int(i[2])
70 if i[2] == '0':
71 tup += 'msg[' + str(bytecount) + ':],'
72 continue
73 if size == 1:
74 n = i[2] * size
75 pack += str(n) + 's'
76 tup += 'tr[' + str(j) + '],'
77 continue
78 pack += format_struct[i[0]] * int(i[2])
79 tup += 'tr[' + str(j) + ':' + str(j + int(i[2])) + '],'
80 j += int(i[2]) - 1
81 else:
82 bytecount += type_size[i[0]]
83 pack += format_struct[i[0]]
84 tup += 'tr[' + str(j) + '],'
85 return pack, bytecount, tup
86
87def get_reply_func(f):
88 if f['name']+'_reply' in func_name:
89 return func_name[f['name']+'_reply']
90 if f['name'].find('_dump') > 0:
91 r = f['name'].replace('_dump','_details')
92 if r in func_name:
93 return func_name[r]
94 return None
95
96def get_enums():
97 # Read enums from stdin
98 enums_by_name = {}
99 enums_by_index = {}
100 i = 1
101 for l in sys.stdin:
102 l = l.replace(',\n','')
103 print l, "=", i
104
105 l = l.replace('VL_API_','').lower()
106 enums_by_name[l] = i
107 enums_by_index[i] = l
108
109 i += 1
110 return enums_by_name, enums_by_index
111
112def get_definitions():
113 # Pass 1
114 func_list = []
115 func_name = {}
116 i = 1
117 for a in cfg.vppapidef:
118 pack, packlen, tup = get_pack(a[1:])
119 func_name[a[0]] = dict([('name', a[0]), ('args', get_args(a[4:])), ('full_args', get_args(a[1:])), ('pack', pack), ('packlen', packlen), ('tup', tup)])
120 func_list.append(func_name[a[0]]) # Indexed by name
121 return func_list, func_name
122
123def generate_c_macros(func_list, enums_by_name):
124 file = open(args.cfile, 'w+')
125 print >>file, "#define foreach_api_msg \\"
126 for f in func_list:
127 if not f['name'] in enums_by_name:
128 continue
129 print >>file, "_(" + f['name'].upper() + ", " + f['name'] + ") \\"
130 print >>file, '''
131void pneum_set_handlers(void) {
132#define _(N,n) \\
133 api_func_table[VL_API_##N] = sizeof(vl_api_##n##_t);
134 foreach_api_msg;
135#undef _
136}
137 '''
138
139#
140# XXX:Deal with empty arrays
141# Print array with a hash of 'decode' and 'multipart'
142# Simplify to do only decode for now. And deduce multipart from _dump?
143#
144def decode_function_print(name, args, pack, packlen, tup):
145
146 print(u'def ' + name + u'_decode(msg):')
147 print(u" n = namedtuple('" + name + "', '" + args + "')" +
148 '''
149 if not n:
150 return None
151 ''')
152 print(u" tr = unpack('" + pack + "', msg[:" + str(packlen) + "])")
153 print(u" r = n._make((" + tup + "))" +
154 '''
155 if not r:
156 return None
157 return r
158 ''')
159
160def function_print(name, id, args, pack, multipart):
161 if not args:
162 args = ""
163 print "def", name + "(async = False):"
164 else:
165 print "def", name + "(" + args + ",async = False):"
166 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
179 print " vpp_api.write(pack('" + pack + "', " + id + ", 0, context, " + args + "))"
180
181 if multipart == True:
182 print " vpp_api.write(pack('>HII', VL_API_CONTROL_PING, 0, context))"
183
184 print '''
185 if not async:
186 results[context]['e'].wait(5)
187 return results[context]['r']
188 return context
189 '''
190
191#
192# Should dynamically create size
193#
194def api_table_print (name, msg_id):
195 f = name + '_decode'
196 print('api_func_table[' + msg_id + '] = ' + f)
197
198#
199# Generate the main Python file
200#
201
202print '''#!/usr/bin/env python3
203
204import sys, time, threading, signal, os, logging
205from struct import *
206from collections import namedtuple
207
208#
209# Import C API shared object
210#
211import vpp_api
212
213context = 0
214results = {}
215waiting_for_reply = False
216
217#
218# XXX: Make this return a unique number
219#
220def get_context(id):
221 global context
222 context += 1
223 return context
224
225def msg_handler(msg):
226 global result, context, event_callback, waiting_for_reply
227 if not msg:
228 logging.warning('vpp_api.read failed')
229 return
230
231 id = unpack('>H', msg[0:2])
232 logging.debug('Received message', id[0])
233 if id[0] == VL_API_RX_THREAD_EXIT:
234 logging.info("We got told to leave")
235 return;
236
237 #
238 # Decode message and returns a tuple.
239 #
240 logging.debug('api_func', api_func_table[id[0]])
241 r = api_func_table[id[0]](msg)
242 if not r:
243 logging.warning('Message decode failed', id[0])
244 return
245
246 if 'context' in r._asdict():
247 if r.context > 0:
248 context = r.context
249
250 #
251 # XXX: Call provided callback for event
252 # Are we guaranteed to not get an event during processing of other messages?
253 # How to differentiate what's a callback message and what not? Context = 0?
254 #
255 logging.debug('R:', context, r, waiting_for_reply)
256 if waiting_for_reply == False:
257 event_callback(r)
258 return
259
260 #
261 # Collect results until control ping
262 #
263 if id[0] == VL_API_CONTROL_PING_REPLY:
264 results[context]['e'].set()
265 waiting_for_reply = False
266 return
267 if not context in results:
268 logging.warning('Not expecting results for this context', context)
269 return
270 if 'm' in results[context]:
271 results[context]['r'].append(r)
272 return
273
274 results[context]['r'] = r
275 results[context]['e'].set()
276 waiting_for_reply = False
277
278def connect(name):
279 signal.alarm(3) # 3 second
280 rv = vpp_api.connect(name, msg_handler)
281 signal.alarm(0)
282 logging.info("Connect:", rv)
283 return rv
284
285def disconnect():
286 rv = vpp_api.disconnect()
287 logging.info("Disconnected")
288 return rv
289
290def register_event_callback(callback):
291 global event_callback
292 event_callback = callback
293'''
294
295enums_by_name, enums_by_index = get_enums()
296func_list, func_name = get_definitions()
297
298#
299# Not needed with the new msg_size field.
300# generate_c_macros(func_list, enums_by_name)
301#
302
303pp = pprint.PrettyPrinter(indent=4)
304#print 'enums_by_index =', pp.pprint(enums_by_index)
305#print 'func_name =', pp.pprint(func_name)
306
307# Pass 2
308
309#
310# 1) The VPE API lacks a clear definition of what messages are reply messages
311# 2) Length is missing, and has to be pre-known or in case of variable sized ones calculated per message type
312#
313for f in func_list:
314 #if f['name'].find('_reply') > 0 or f['name'].find('_details') > 0:
315 decode_function_print(f['name'], f['full_args'], f['pack'], f['packlen'], f['tup'])
316
317 #r = get_reply_func(f)
318 #if not r:
319 # #
320 # # XXX: Functions here are not taken care of. E.g. events
321 # #
322 # print('Missing function', f)
323 # continue
324
325 if f['name'].find('_dump') > 0:
326 f['multipart'] = True
327 else:
328 f['multipart'] = False
329 msg_id_in = 'VL_API_' + f['name'].upper()
330 function_print(f['name'], msg_id_in, f['args'], f['pack'], f['multipart'])
331
332
333print "api_func_table = [0] * 10000"
334for f in func_list:
335 # if f['name'].find('_reply') > 0 or f['name'].find('_details') > 0:
336 msg_id_in = 'VL_API_' + f['name'].upper()
337 api_table_print(f['name'], msg_id_in)