blob: b18eda42f6b9b3d6b522ba65b5c5411863adff6e [file] [log] [blame]
Klement Sekera958b7502017-09-28 06:31:53 +02001#!/usr/bin/env python2
Klement Sekera8f2a4ea2017-05-04 06:15:18 +02002
3import json
4
5
6def msg_is_reply(name):
7 return name.endswith('_reply') or name.endswith('_details') \
8 or name.endswith('_event') or name.endswith('_counters')
9
10
11class ParseError (Exception):
12 pass
13
14
15magic_prefix = "vl_api_"
16magic_suffix = "_t"
17
18
19def remove_magic(what):
20 if what.startswith(magic_prefix) and what.endswith(magic_suffix):
21 return what[len(magic_prefix): - len(magic_suffix)]
22 return what
23
24
Klement Sekera958b7502017-09-28 06:31:53 +020025class Field(object):
Klement Sekera8f2a4ea2017-05-04 06:15:18 +020026
27 def __init__(
28 self,
29 field_name,
30 field_type,
31 array_len=None,
32 nelem_field=None):
33 self.name = field_name
34 self.type = field_type
35 self.len = array_len
36 self.nelem_field = nelem_field
37
38 def __str__(self):
39 if self.len is None:
40 return "name: %s, type: %s" % (self.name, self.type)
41 elif self.len > 0:
42 return "name: %s, type: %s, length: %s" % (self.name, self.type,
43 self.len)
44 else:
45 return ("name: %s, type: %s, variable length stored in: %s" %
46 (self.name, self.type, self.nelem_field))
47
48
Klement Sekera958b7502017-09-28 06:31:53 +020049class Type(object):
Klement Sekera8f2a4ea2017-05-04 06:15:18 +020050 def __init__(self, name):
51 self.name = name
52
Klement Sekera32a9d7b2017-12-10 05:15:41 +010053 def __str__(self):
54 return self.name
55
Klement Sekera8f2a4ea2017-05-04 06:15:18 +020056
57class SimpleType (Type):
58
59 def __init__(self, name):
Klement Sekera958b7502017-09-28 06:31:53 +020060 super(SimpleType, self).__init__(name)
Klement Sekera8f2a4ea2017-05-04 06:15:18 +020061
62 def __str__(self):
63 return self.name
64
65
Klement Sekera8b6b5ab2018-05-03 14:27:42 +020066def get_msg_header_defs(struct_type_class, field_class, typedict, logger):
Klement Sekera8f2a4ea2017-05-04 06:15:18 +020067 return [
68 struct_type_class(['msg_header1_t',
69 ['u16', '_vl_msg_id'],
70 ['u32', 'context'],
71 ],
Klement Sekera8b6b5ab2018-05-03 14:27:42 +020072 typedict, field_class, logger
Klement Sekera8f2a4ea2017-05-04 06:15:18 +020073 ),
74 struct_type_class(['msg_header2_t',
75 ['u16', '_vl_msg_id'],
76 ['u32', 'client_index'],
77 ['u32', 'context'],
78 ],
Klement Sekera8b6b5ab2018-05-03 14:27:42 +020079 typedict, field_class, logger
Klement Sekera8f2a4ea2017-05-04 06:15:18 +020080 ),
81 ]
82
83
Klement Sekera958b7502017-09-28 06:31:53 +020084class Struct(object):
Klement Sekera8f2a4ea2017-05-04 06:15:18 +020085
86 def __init__(self, name, fields):
87 self.name = name
88 self.fields = fields
89 self.field_names = [n.name for n in self.fields]
90
Klement Sekera32a9d7b2017-12-10 05:15:41 +010091 def __str__(self):
92 return "[%s]" % "], [".join([str(f) for f in self.fields])
93
Klement Sekera8f2a4ea2017-05-04 06:15:18 +020094
Klement Sekera958b7502017-09-28 06:31:53 +020095class Message(object):
Klement Sekera8f2a4ea2017-05-04 06:15:18 +020096
97 def __init__(self, logger, definition, typedict,
98 struct_type_class, simple_type_class, field_class):
Klement Sekeradc15be22017-06-12 06:49:33 +020099 self.request = None
Klement Sekera8f2a4ea2017-05-04 06:15:18 +0200100 self.logger = logger
101 m = definition
102 logger.debug("Parsing message definition `%s'" % m)
103 name = m[0]
104 self.name = name
105 logger.debug("Message name is `%s'" % name)
106 ignore = True
107 self.header = None
108 fields = []
109 for header in get_msg_header_defs(struct_type_class, field_class,
Klement Sekera8b6b5ab2018-05-03 14:27:42 +0200110 typedict, logger):
Klement Sekera8f2a4ea2017-05-04 06:15:18 +0200111 logger.debug("Probing header `%s'" % header.name)
112 if header.is_part_of_def(m[1:]):
113 self.header = header
114 logger.debug("Found header `%s'" % header.name)
115 fields.append(field_class(field_name='header',
116 field_type=self.header))
117 ignore = False
118 break
119 if ignore and not msg_is_reply(name):
120 raise ParseError("While parsing message `%s': could not find all "
121 "common header fields" % name)
122 for field in m[1:]:
123 if len(field) == 1 and 'crc' in field:
124 self.crc = field['crc']
125 logger.debug("Found CRC `%s'" % self.crc)
126 continue
127 else:
128 field_type = field[0]
129 if field_type in typedict:
130 field_type = typedict[field_type]
131 else:
Klement Sekera32a9d7b2017-12-10 05:15:41 +0100132 mundane_field_type = remove_magic(field_type)
133 if mundane_field_type in typedict:
134 field_type = typedict[mundane_field_type]
135 else:
136 raise ParseError(
137 "While parsing message `%s': could not find "
138 "type by magic name `%s' nor by mundane name "
139 "`%s'" % (name, field_type, mundane_field_type))
Klement Sekera8b6b5ab2018-05-03 14:27:42 +0200140 logger.debug("Parsing message field `%s'" % field)
Klement Sekera8f2a4ea2017-05-04 06:15:18 +0200141 if len(field) == 2:
142 if self.header is not None and\
143 self.header.has_field(field[1]):
144 continue
145 p = field_class(field_name=field[1],
146 field_type=field_type)
147 elif len(field) == 3:
148 if field[2] == 0:
149 raise ParseError(
150 "While parsing message `%s': variable length "
151 "array `%s' doesn't have reference to member "
152 "containing the actual length" % (
153 name, field[1]))
154 p = field_class(
155 field_name=field[1],
156 field_type=field_type,
157 array_len=field[2])
158 elif len(field) == 4:
159 nelem_field = None
160 for f in fields:
161 if f.name == field[3]:
162 nelem_field = f
163 if nelem_field is None:
164 raise ParseError(
165 "While parsing message `%s': couldn't find "
166 "variable length array `%s' member containing "
167 "the actual length `%s'" % (
168 name, field[1], field[3]))
169 p = field_class(
170 field_name=field[1],
171 field_type=field_type,
172 array_len=field[2],
173 nelem_field=nelem_field)
174 else:
175 raise Exception("Don't know how to parse message "
176 "definition for message `%s': `%s'" %
177 (m, m[1:]))
178 logger.debug("Parsed field `%s'" % p)
179 fields.append(p)
180 self.fields = fields
181
182 def is_dump(self):
183 return self.name.endswith('_dump')
184
185 def is_reply(self):
186 return msg_is_reply(self.name)
187
188
189class StructType (Type, Struct):
190
Klement Sekera8b6b5ab2018-05-03 14:27:42 +0200191 def __init__(self, definition, typedict, field_class, logger):
Klement Sekera8f2a4ea2017-05-04 06:15:18 +0200192 t = definition
Klement Sekera8b6b5ab2018-05-03 14:27:42 +0200193 logger.debug("Parsing struct definition `%s'" % t)
Klement Sekera8f2a4ea2017-05-04 06:15:18 +0200194 name = t[0]
195 fields = []
196 for field in t[1:]:
197 if len(field) == 1 and 'crc' in field:
198 self.crc = field['crc']
199 continue
Klement Sekera329a1c12018-02-25 18:48:04 +0100200 field_type = field[0]
201 if field_type in typedict:
202 field_type = typedict[field_type]
203 else:
204 mundane_field_type = remove_magic(field_type)
205 if mundane_field_type in typedict:
206 field_type = typedict[mundane_field_type]
207 else:
208 raise ParseError(
209 "While parsing message `%s': could not find "
210 "type by magic name `%s' nor by mundane name "
211 "`%s'" % (name, field_type, mundane_field_type))
Klement Sekera8b6b5ab2018-05-03 14:27:42 +0200212 logger.debug("Parsing type field `%s'" % field)
Klement Sekera329a1c12018-02-25 18:48:04 +0100213 if len(field) == 2:
Klement Sekera8f2a4ea2017-05-04 06:15:18 +0200214 p = field_class(field_name=field[1],
Klement Sekera329a1c12018-02-25 18:48:04 +0100215 field_type=field_type)
Klement Sekera8f2a4ea2017-05-04 06:15:18 +0200216 elif len(field) == 3:
217 if field[2] == 0:
218 raise ParseError("While parsing type `%s': array `%s' has "
219 "variable length" % (name, field[1]))
220 p = field_class(field_name=field[1],
Klement Sekera329a1c12018-02-25 18:48:04 +0100221 field_type=field_type,
Klement Sekera8f2a4ea2017-05-04 06:15:18 +0200222 array_len=field[2])
Klement Sekera8b6b5ab2018-05-03 14:27:42 +0200223 elif len(field) == 4:
224 nelem_field = None
225 for f in fields:
226 if f.name == field[3]:
227 nelem_field = f
228 if nelem_field is None:
229 raise ParseError(
230 "While parsing message `%s': couldn't find "
231 "variable length array `%s' member containing "
232 "the actual length `%s'" % (
233 name, field[1], field[3]))
234 p = field_class(field_name=field[1],
235 field_type=field_type,
236 array_len=field[2],
237 nelem_field=nelem_field)
Klement Sekera8f2a4ea2017-05-04 06:15:18 +0200238 else:
239 raise ParseError(
Klement Sekera8b6b5ab2018-05-03 14:27:42 +0200240 "Don't know how to parse field `%s' of type definition "
241 "for type `%s'" % (field, t))
Klement Sekera8f2a4ea2017-05-04 06:15:18 +0200242 fields.append(p)
243 Type.__init__(self, name)
244 Struct.__init__(self, name, fields)
245
Klement Sekera32a9d7b2017-12-10 05:15:41 +0100246 def __str__(self):
247 return "StructType(%s, %s)" % (Type.__str__(self),
248 Struct.__str__(self))
249
Klement Sekera8f2a4ea2017-05-04 06:15:18 +0200250 def has_field(self, name):
251 return name in self.field_names
252
253 def is_part_of_def(self, definition):
254 for idx in range(len(self.fields)):
255 field = definition[idx]
256 p = self.fields[idx]
257 if field[1] != p.name:
258 return False
259 if field[0] != p.type.name:
260 raise ParseError(
261 "Unexpected field type `%s' (should be `%s'), "
262 "while parsing msg/def/field `%s/%s/%s'" %
263 (field[0], p.type, p.name, definition, field))
264 return True
265
266
Klement Sekera958b7502017-09-28 06:31:53 +0200267class JsonParser(object):
Klement Sekera8f2a4ea2017-05-04 06:15:18 +0200268 def __init__(self, logger, files, simple_type_class=SimpleType,
269 struct_type_class=StructType, field_class=Field,
270 message_class=Message):
271 self.messages = {}
272 self.types = {
273 x: simple_type_class(x) for x in [
274 'i8', 'i16', 'i32', 'i64',
275 'u8', 'u16', 'u32', 'u64',
276 'f64'
277 ]
278 }
279
280 self.simple_type_class = simple_type_class
281 self.struct_type_class = struct_type_class
282 self.field_class = field_class
283 self.message_class = message_class
284
285 self.exceptions = []
286 self.json_files = []
287 self.types_by_json = {}
288 self.messages_by_json = {}
289 self.logger = logger
290 for f in files:
291 self.parse_json_file(f)
292 self.finalize_parsing()
293
294 def parse_json_file(self, path):
295 self.logger.info("Parsing json api file: `%s'" % path)
296 self.json_files.append(path)
Ole Troan52ca7562018-03-06 17:45:32 +0100297 self.types_by_json[path] = []
Klement Sekera8f2a4ea2017-05-04 06:15:18 +0200298 self.messages_by_json[path] = {}
299 with open(path) as f:
300 j = json.load(f)
301 for t in j['types']:
302 try:
303 type_ = self.struct_type_class(t, self.types,
Klement Sekera8b6b5ab2018-05-03 14:27:42 +0200304 self.field_class,
305 self.logger)
Klement Sekera8f2a4ea2017-05-04 06:15:18 +0200306 if type_.name in self.types:
307 raise ParseError("Duplicate type `%s'" % type_.name)
308 except ParseError as e:
309 self.exceptions.append(e)
310 continue
311 self.types[type_.name] = type_
Ole Troan52ca7562018-03-06 17:45:32 +0100312 self.types_by_json[path].append(type_)
Klement Sekera32a9d7b2017-12-10 05:15:41 +0100313 self.logger.debug("Parsed type: %s" % type_)
Klement Sekera8b6b5ab2018-05-03 14:27:42 +0200314 prev_length = len(self.messages)
315 processed = []
316 while True:
317 exceptions = []
318 for m in j['messages']:
319 if m in processed:
320 continue
321 try:
322 msg = self.message_class(self.logger, m, self.types,
323 self.struct_type_class,
324 self.simple_type_class,
325 self.field_class)
326 if msg.name in self.messages:
327 raise ParseError(
328 "Duplicate message `%s'" % msg.name)
329 except ParseError as e:
330 exceptions.append(e)
331 continue
332 self.messages[msg.name] = msg
333 self.messages_by_json[path][msg.name] = msg
334 processed.append(m)
335 if prev_length == len(self.messages):
336 # cannot make forward progress ...
337 self.exceptions.extend(exceptions)
338 break
339 prev_length = len(self.messages)
Klement Sekera8f2a4ea2017-05-04 06:15:18 +0200340
341 def get_reply(self, message):
342 if self.messages[message].is_dump():
343 return self.messages["%s_details" % message[:-len("_dump")]]
344 return self.messages["%s_reply" % message]
345
346 def finalize_parsing(self):
347 if len(self.messages) == 0:
348 for e in self.exceptions:
Damjan Marion4c64b6e2018-08-26 18:14:46 +0200349 self.logger.warning(e)
Klement Sekera8f2a4ea2017-05-04 06:15:18 +0200350 for jn, j in self.messages_by_json.items():
351 remove = []
352 for n, m in j.items():
353 try:
354 if not m.is_reply():
355 try:
356 m.reply = self.get_reply(n)
Klement Sekeradc15be22017-06-12 06:49:33 +0200357 m.reply.request = m
Klement Sekera8f2a4ea2017-05-04 06:15:18 +0200358 except:
359 raise ParseError(
360 "Cannot find reply to message `%s'" % n)
361 except ParseError as e:
362 self.exceptions.append(e)
363 remove.append(n)
364
365 self.messages_by_json[jn] = {
366 k: v for k, v in j.items() if k not in remove}