Ole Troan | 6855f6c | 2016-04-09 03:16:30 +0200 | [diff] [blame] | 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', |
Filip Tehlar | 69a9b76 | 2016-09-23 10:00:52 +0200 | [diff] [blame^] | 38 | 'vl_api_lisp_adjacency_t' : 'B' * 35, |
Ole Troan | 6855f6c | 2016-04-09 03:16:30 +0200 | [diff] [blame] | 39 | }; |
Ole Troan | fc6cf28 | 2016-04-25 12:36:02 +0200 | [diff] [blame] | 40 | # |
| 41 | # NB: If new types are introduced in vpe.api, these must be updated. |
| 42 | # |
Ole Troan | 6855f6c | 2016-04-09 03:16:30 +0200 | [diff] [blame] | 43 | type_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 Tehlar | 69a9b76 | 2016-09-23 10:00:52 +0200 | [diff] [blame^] | 51 | 'vl_api_lisp_adjacency_t' : 35, |
Ole Troan | 6855f6c | 2016-04-09 03:16:30 +0200 | [diff] [blame] | 52 | }; |
| 53 | |
| 54 | def get_args(t): |
Ole Troan | fc6cf28 | 2016-04-25 12:36:02 +0200 | [diff] [blame] | 55 | argslist = [] |
Ole Troan | 6855f6c | 2016-04-09 03:16:30 +0200 | [diff] [blame] | 56 | for i in t: |
Ole Troan | e6749e4 | 2016-04-28 12:50:20 +0200 | [diff] [blame] | 57 | if i[1][0] == '_': |
| 58 | argslist.append(i[1][1:]) |
| 59 | else: |
| 60 | argslist.append(i[1]) |
| 61 | |
Ole Troan | fc6cf28 | 2016-04-25 12:36:02 +0200 | [diff] [blame] | 62 | return argslist |
Ole Troan | 6855f6c | 2016-04-09 03:16:30 +0200 | [diff] [blame] | 63 | |
| 64 | def get_pack(t): |
Ole Troan | fc6cf28 | 2016-04-25 12:36:02 +0200 | [diff] [blame] | 65 | zeroarray = False |
Ole Troan | 6855f6c | 2016-04-09 03:16:30 +0200 | [diff] [blame] | 66 | bytecount = 0 |
| 67 | pack = '>' |
| 68 | tup = u'' |
| 69 | j = -1 |
| 70 | for i in t: |
| 71 | j += 1 |
Marek Gradzki | fa42e25 | 2016-06-15 16:38:33 +0200 | [diff] [blame] | 72 | if len(i) is 3 or len(i) is 4: # TODO: add support for variable length arrays (VPP-162) |
Ole Troan | 6855f6c | 2016-04-09 03:16:30 +0200 | [diff] [blame] | 73 | size = type_size[i[0]] |
| 74 | bytecount += size * int(i[2]) |
Ole Troan | fc6cf28 | 2016-04-25 12:36:02 +0200 | [diff] [blame] | 75 | # Check if we have a zero length array |
Ole Troan | 6855f6c | 2016-04-09 03:16:30 +0200 | [diff] [blame] | 76 | if i[2] == '0': |
| 77 | tup += 'msg[' + str(bytecount) + ':],' |
Ole Troan | fc6cf28 | 2016-04-25 12:36:02 +0200 | [diff] [blame] | 78 | zeroarray = True |
Ole Troan | 6855f6c | 2016-04-09 03:16:30 +0200 | [diff] [blame] | 79 | 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 Troan | fc6cf28 | 2016-04-25 12:36:02 +0200 | [diff] [blame] | 92 | return pack, bytecount, tup, zeroarray |
Ole Troan | 6855f6c | 2016-04-09 03:16:30 +0200 | [diff] [blame] | 93 | |
| 94 | def 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 | |
| 103 | def 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 | |
| 119 | def get_definitions(): |
| 120 | # Pass 1 |
| 121 | func_list = [] |
| 122 | func_name = {} |
| 123 | i = 1 |
Marek Gradzki | 101759c | 2016-09-29 13:20:52 +0200 | [diff] [blame] | 124 | for a in cfg.messages: |
Ole Troan | fc6cf28 | 2016-04-25 12:36:02 +0200 | [diff] [blame] | 125 | 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 Troan | 6855f6c | 2016-04-09 03:16:30 +0200 | [diff] [blame] | 128 | func_list.append(func_name[a[0]]) # Indexed by name |
| 129 | return func_list, func_name |
| 130 | |
| 131 | def 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, ''' |
| 139 | void 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 Troan | 6855f6c | 2016-04-09 03:16:30 +0200 | [diff] [blame] | 148 | # Print array with a hash of 'decode' and 'multipart' |
| 149 | # Simplify to do only decode for now. And deduce multipart from _dump? |
| 150 | # |
| 151 | def decode_function_print(name, args, pack, packlen, tup): |
| 152 | |
| 153 | print(u'def ' + name + u'_decode(msg):') |
Ole Troan | fc6cf28 | 2016-04-25 12:36:02 +0200 | [diff] [blame] | 154 | print(u" n = namedtuple('" + name + "', '" + ', '.join(args) + "')" + |
Ole Troan | 6855f6c | 2016-04-09 03:16:30 +0200 | [diff] [blame] | 155 | ''' |
| 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 Troan | fc6cf28 | 2016-04-25 12:36:02 +0200 | [diff] [blame] | 167 | def function_print(name, id, args, pack, multipart, zeroarray): |
| 168 | if len(args) < 4: |
Ole Troan | 6855f6c | 2016-04-09 03:16:30 +0200 | [diff] [blame] | 169 | print "def", name + "(async = False):" |
| 170 | else: |
Ole Troan | fc6cf28 | 2016-04-25 12:36:02 +0200 | [diff] [blame] | 171 | print "def", name + "(" + ', '.join(args[3:]) + ", async = False):" |
Ole Troan | 6855f6c | 2016-04-09 03:16:30 +0200 | [diff] [blame] | 172 | 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 Troan | fc6cf28 | 2016-04-25 12:36:02 +0200 | [diff] [blame] | 185 | 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 Troan | 6855f6c | 2016-04-09 03:16:30 +0200 | [diff] [blame] | 189 | |
| 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 | # |
| 203 | def 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 Troan | d06b9f9 | 2016-04-25 13:11:19 +0200 | [diff] [blame] | 211 | print ''' |
Ole Troan | 6855f6c | 2016-04-09 03:16:30 +0200 | [diff] [blame] | 212 | |
Ole Troan | fc6cf28 | 2016-04-25 12:36:02 +0200 | [diff] [blame] | 213 | # |
| 214 | # AUTO-GENERATED FILE. PLEASE DO NOT EDIT. |
| 215 | # |
Ole Troan | 6855f6c | 2016-04-09 03:16:30 +0200 | [diff] [blame] | 216 | import sys, time, threading, signal, os, logging |
| 217 | from struct import * |
| 218 | from collections import namedtuple |
| 219 | |
| 220 | # |
| 221 | # Import C API shared object |
| 222 | # |
| 223 | import vpp_api |
| 224 | |
| 225 | context = 0 |
| 226 | results = {} |
| 227 | waiting_for_reply = False |
| 228 | |
| 229 | # |
| 230 | # XXX: Make this return a unique number |
| 231 | # |
| 232 | def get_context(id): |
| 233 | global context |
| 234 | context += 1 |
| 235 | return context |
| 236 | |
| 237 | def 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 | |
| 290 | def 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 | |
| 297 | def disconnect(): |
| 298 | rv = vpp_api.disconnect() |
| 299 | logging.info("Disconnected") |
| 300 | return rv |
| 301 | |
| 302 | def register_event_callback(callback): |
| 303 | global event_callback |
| 304 | event_callback = callback |
| 305 | ''' |
| 306 | |
| 307 | enums_by_name, enums_by_index = get_enums() |
| 308 | func_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 | |
| 315 | pp = 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 | # |
| 325 | for f in func_list: |
| 326 | #if f['name'].find('_reply') > 0 or f['name'].find('_details') > 0: |
Ole Troan | fc6cf28 | 2016-04-25 12:36:02 +0200 | [diff] [blame] | 327 | decode_function_print(f['name'], f['args'], f['pack'], f['packlen'], f['tup']) |
Ole Troan | 6855f6c | 2016-04-09 03:16:30 +0200 | [diff] [blame] | 328 | |
| 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 Troan | fc6cf28 | 2016-04-25 12:36:02 +0200 | [diff] [blame] | 342 | function_print(f['name'], msg_id_in, f['args'], f['pack'], f['multipart'], f['zeroarray']) |
Ole Troan | 6855f6c | 2016-04-09 03:16:30 +0200 | [diff] [blame] | 343 | |
| 344 | |
| 345 | print "api_func_table = [0] * 10000" |
| 346 | for 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) |