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