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