bbc9c3290aca44add1455910d3cc3e8bf9816382
[vpp.git] / vpp-api / python / pneum / api-gen.py
1 #!/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
17 import argparse, sys, os, importlib, pprint
18
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()
23
24 sys.path.append(".")
25
26 inputfile = args.inputfile.replace('.py', '')
27 cfg = importlib.import_module(inputfile, package=None)
28
29 # https://docs.python.org/3/library/struct.html
30 format_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                  };
39 type_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
49 def 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
60 def 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
87 def 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
96 def 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
112 def 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
123 def 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, '''
131 void 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 #
144 def 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
160 def 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 #
194 def 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
202 print '''#!/usr/bin/env python3
203
204 import sys, time, threading, signal, os, logging
205 from struct import *
206 from collections import namedtuple
207
208 #
209 # Import C API shared object
210 #
211 import vpp_api
212
213 context = 0
214 results = {}
215 waiting_for_reply = False
216
217 #
218 # XXX: Make this return a unique number
219 #
220 def get_context(id):
221     global context
222     context += 1
223     return context
224
225 def 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
278 def 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
285 def disconnect():
286     rv = vpp_api.disconnect()
287     logging.info("Disconnected")
288     return rv
289
290 def register_event_callback(callback):
291     global event_callback
292     event_callback = callback
293 '''
294
295 enums_by_name, enums_by_index = get_enums()
296 func_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
303 pp = 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 #
313 for 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
333 print "api_func_table = [0] * 10000"
334 for 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)