Dump routes (VPP-500)
[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_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,
40                  };
41 #
42 # NB: If new types are introduced in vpe.api, these must be updated.
43 #
44 type_size = {'u8':   1,
45              'u16' : 2,
46              'u32' : 4,
47              'i32' : 4,
48              'u64' : 8,
49              'f64' : 8,
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,
54 };
55
56 def get_args(t):
57     argslist = []
58     for i in t:
59         if i[1][0] == '_':
60             argslist.append(i[1][1:])
61         else:
62             argslist.append(i[1])
63
64     return argslist
65
66 def get_pack(t):
67     zeroarray = False
68     bytecount = 0
69     pack = '>'
70     tup = u''
71     j = -1
72     for i in t:
73         j += 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
78             if i[2] == '0':
79                 tup += 'msg[' + str(bytecount) + ':],'
80                 zeroarray = True
81                 continue
82             if size == 1:
83                 n = i[2] * size
84                 pack += str(n) + 's'
85                 tup += 'tr[' + str(j) + '],'
86                 continue
87             pack += format_struct[i[0]] * int(i[2])
88             tup += 'tr[' + str(j) + ':' + str(j + int(i[2])) + '],'
89             j += int(i[2]) - 1
90         else:
91             bytecount += type_size[i[0]]
92             pack += format_struct[i[0]]
93             tup += 'tr[' + str(j) + '],'
94     return pack, bytecount, tup, zeroarray
95
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')
101         if r in func_name:
102             return func_name[r]
103     return None
104
105 def get_enums():
106     # Read enums from stdin
107     enums_by_name = {}
108     enums_by_index = {}
109     i = 1
110     for l in sys.stdin:
111         l = l.replace(',\n','')
112         print l, "=", i
113
114         l = l.replace('VL_API_','').lower()
115         enums_by_name[l] = i
116         enums_by_index[i] = l
117
118         i += 1
119     return enums_by_name, enums_by_index
120
121 def get_definitions():
122     # Pass 1
123     func_list = []
124     func_name = {}
125     i = 1
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
132
133 def generate_c_macros(func_list, enums_by_name):
134     file = open(args.cfile, 'w+')
135     print >>file, "#define foreach_api_msg \\"
136     for f in func_list:
137         if not f['name'] in enums_by_name:
138             continue
139         print >>file, "_(" + f['name'].upper() + ", " + f['name'] + ") \\"
140     print >>file, '''
141 void pneum_set_handlers(void) {
142 #define _(N,n)                                                  \\
143   api_func_table[VL_API_##N] = sizeof(vl_api_##n##_t);
144   foreach_api_msg;
145 #undef _
146 }
147     '''
148
149 #
150 # Print array with a hash of 'decode' and 'multipart'
151 # Simplify to do only decode for now. And deduce multipart from _dump?
152 #
153 def decode_function_print(name, args, pack, packlen, tup):
154
155     print(u'def ' + name + u'_decode(msg):')
156     print(u"    n = namedtuple('" + name + "', '" + ', '.join(args) + "')" +
157     '''
158     if not n:
159         return None
160     ''')
161     print(u"    tr = unpack('" + pack + "', msg[:" + str(packlen) + "])")
162     print(u"    r = n._make((" + tup + "))" +
163     '''
164     if not r:
165         return None
166     return r
167     ''')
168
169 def function_print(name, id, args, pack, multipart, zeroarray):
170     if len(args) < 4:
171         print "def", name + "(async = False):"
172     else:
173         print "def", name + "(" + ', '.join(args[3:]) + ", async = False):"
174     print "    global waiting_for_reply"
175     print "    context = get_context(" + id + ")"
176
177     print '''
178     results[context] = {}
179     results[context]['e'] = threading.Event()
180     results[context]['e'].clear()
181     results[context]['r'] = []
182     waiting_for_reply = True
183     '''
184     if multipart == True:
185         print "    results[context]['m'] = True"
186
187     if zeroarray == True:
188         print "    vpp_api.write(pack('" + pack + "', " + id + ", 0, context, " + ', '.join(args[3:-1]) + ") + " + args[-1] + ")"
189     else:
190         print "    vpp_api.write(pack('" + pack + "', " + id + ", 0, context, " + ', '.join(args[3:]) + "))"
191
192     if multipart == True:
193         print "    vpp_api.write(pack('>HII', VL_API_CONTROL_PING, 0, context))"
194
195     print '''
196     if not async:
197         results[context]['e'].wait(5)
198         return results[context]['r']
199     return context
200     '''
201
202 #
203 # Should dynamically create size
204 #
205 def api_table_print (name, msg_id):
206     f = name + '_decode'
207     print('api_func_table[' + msg_id + '] = ' + f)
208
209 #
210 # Generate the main Python file
211 #
212
213 print '''
214
215 #
216 # AUTO-GENERATED FILE. PLEASE DO NOT EDIT.
217 #
218 import sys, time, threading, signal, os, logging
219 from struct import *
220 from collections import namedtuple
221
222 #
223 # Import C API shared object
224 #
225 import vpp_api
226
227 context = 0
228 results = {}
229 waiting_for_reply = False
230
231 #
232 # XXX: Make this return a unique number
233 #
234 def get_context(id):
235     global context
236     context += 1
237     return context
238
239 def msg_handler(msg):
240     global result, context, event_callback, waiting_for_reply
241     if not msg:
242         logging.warning('vpp_api.read failed')
243         return
244
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")
249         return;
250
251     #
252     # Decode message and returns a tuple.
253     #
254     logging.debug('api_func', api_func_table[id[0]])
255     r = api_func_table[id[0]](msg)
256     if not r:
257         logging.warning('Message decode failed', id[0])
258         return
259
260     if 'context' in r._asdict():
261         if r.context > 0:
262             context = r.context
263
264     #
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?
268     #
269     logging.debug('R:', context, r, waiting_for_reply)
270     if waiting_for_reply == False:
271         event_callback(r)
272         return
273
274     #
275     # Collect results until control ping
276     #
277     if id[0] == VL_API_CONTROL_PING_REPLY:
278         results[context]['e'].set()
279         waiting_for_reply = False
280         return
281     if not context in results:
282         logging.warning('Not expecting results for this context', context)
283         return
284     if 'm' in results[context]:
285         results[context]['r'].append(r)
286         return
287
288     results[context]['r'] = r
289     results[context]['e'].set()
290     waiting_for_reply = False
291
292 def connect(name):
293     signal.alarm(3) # 3 second
294     rv = vpp_api.connect(name, msg_handler)
295     signal.alarm(0)
296     logging.info("Connect:", rv)
297     return rv
298
299 def disconnect():
300     rv = vpp_api.disconnect()
301     logging.info("Disconnected")
302     return rv
303
304 def register_event_callback(callback):
305     global event_callback
306     event_callback = callback
307 '''
308
309 enums_by_name, enums_by_index = get_enums()
310 func_list, func_name = get_definitions()
311
312 #
313 # Not needed with the new msg_size field.
314 # generate_c_macros(func_list, enums_by_name)
315 #
316
317 pp = pprint.PrettyPrinter(indent=4)
318 #print 'enums_by_index =', pp.pprint(enums_by_index)
319 #print 'func_name =', pp.pprint(func_name)
320
321 # Pass 2
322
323 #
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
326 #
327 for f in func_list:
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'])
330
331     #r = get_reply_func(f)
332     #if not r:
333     #    #
334     #    # XXX: Functions here are not taken care of. E.g. events
335     #    #
336     #    print('Missing function', f)
337     #    continue
338
339     if f['name'].find('_dump') > 0:
340         f['multipart'] = True
341     else:
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'])
345
346
347 print "api_func_table = [0] * 10000"
348 for f in func_list:
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)