blob: 4e62720d37e3800d5faf66bf3f8087e1556354e7 [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
53
54class SimpleType (Type):
55
56 def __init__(self, name):
Klement Sekera958b7502017-09-28 06:31:53 +020057 super(SimpleType, self).__init__(name)
Klement Sekera8f2a4ea2017-05-04 06:15:18 +020058
59 def __str__(self):
60 return self.name
61
62
63def get_msg_header_defs(struct_type_class, field_class, typedict):
64 return [
65 struct_type_class(['msg_header1_t',
66 ['u16', '_vl_msg_id'],
67 ['u32', 'context'],
68 ],
69 typedict, field_class
70 ),
71 struct_type_class(['msg_header2_t',
72 ['u16', '_vl_msg_id'],
73 ['u32', 'client_index'],
74 ['u32', 'context'],
75 ],
76 typedict, field_class
77 ),
78 ]
79
80
Klement Sekera958b7502017-09-28 06:31:53 +020081class Struct(object):
Klement Sekera8f2a4ea2017-05-04 06:15:18 +020082
83 def __init__(self, name, fields):
84 self.name = name
85 self.fields = fields
86 self.field_names = [n.name for n in self.fields]
87
88
Klement Sekera958b7502017-09-28 06:31:53 +020089class Message(object):
Klement Sekera8f2a4ea2017-05-04 06:15:18 +020090
91 def __init__(self, logger, definition, typedict,
92 struct_type_class, simple_type_class, field_class):
Klement Sekeradc15be22017-06-12 06:49:33 +020093 self.request = None
Klement Sekera8f2a4ea2017-05-04 06:15:18 +020094 self.logger = logger
95 m = definition
96 logger.debug("Parsing message definition `%s'" % m)
97 name = m[0]
98 self.name = name
99 logger.debug("Message name is `%s'" % name)
100 ignore = True
101 self.header = None
102 fields = []
103 for header in get_msg_header_defs(struct_type_class, field_class,
104 typedict):
105 logger.debug("Probing header `%s'" % header.name)
106 if header.is_part_of_def(m[1:]):
107 self.header = header
108 logger.debug("Found header `%s'" % header.name)
109 fields.append(field_class(field_name='header',
110 field_type=self.header))
111 ignore = False
112 break
113 if ignore and not msg_is_reply(name):
114 raise ParseError("While parsing message `%s': could not find all "
115 "common header fields" % name)
116 for field in m[1:]:
117 if len(field) == 1 and 'crc' in field:
118 self.crc = field['crc']
119 logger.debug("Found CRC `%s'" % self.crc)
120 continue
121 else:
122 field_type = field[0]
123 if field_type in typedict:
124 field_type = typedict[field_type]
125 else:
126 field_type = typedict[remove_magic(field_type)]
127 if len(field) == 2:
128 if self.header is not None and\
129 self.header.has_field(field[1]):
130 continue
131 p = field_class(field_name=field[1],
132 field_type=field_type)
133 elif len(field) == 3:
134 if field[2] == 0:
135 raise ParseError(
136 "While parsing message `%s': variable length "
137 "array `%s' doesn't have reference to member "
138 "containing the actual length" % (
139 name, field[1]))
140 p = field_class(
141 field_name=field[1],
142 field_type=field_type,
143 array_len=field[2])
144 elif len(field) == 4:
145 nelem_field = None
146 for f in fields:
147 if f.name == field[3]:
148 nelem_field = f
149 if nelem_field is None:
150 raise ParseError(
151 "While parsing message `%s': couldn't find "
152 "variable length array `%s' member containing "
153 "the actual length `%s'" % (
154 name, field[1], field[3]))
155 p = field_class(
156 field_name=field[1],
157 field_type=field_type,
158 array_len=field[2],
159 nelem_field=nelem_field)
160 else:
161 raise Exception("Don't know how to parse message "
162 "definition for message `%s': `%s'" %
163 (m, m[1:]))
164 logger.debug("Parsed field `%s'" % p)
165 fields.append(p)
166 self.fields = fields
167
168 def is_dump(self):
169 return self.name.endswith('_dump')
170
171 def is_reply(self):
172 return msg_is_reply(self.name)
173
174
175class StructType (Type, Struct):
176
177 def __init__(self, definition, typedict, field_class):
178 t = definition
179 name = t[0]
180 fields = []
181 for field in t[1:]:
182 if len(field) == 1 and 'crc' in field:
183 self.crc = field['crc']
184 continue
185 elif len(field) == 2:
186 p = field_class(field_name=field[1],
187 field_type=typedict[field[0]])
188 elif len(field) == 3:
189 if field[2] == 0:
190 raise ParseError("While parsing type `%s': array `%s' has "
191 "variable length" % (name, field[1]))
192 p = field_class(field_name=field[1],
193 field_type=typedict[field[0]],
194 array_len=field[2])
195 else:
196 raise ParseError(
197 "Don't know how to parse type definition for "
198 "type `%s': `%s'" % (t, t[1:]))
199 fields.append(p)
200 Type.__init__(self, name)
201 Struct.__init__(self, name, fields)
202
203 def has_field(self, name):
204 return name in self.field_names
205
206 def is_part_of_def(self, definition):
207 for idx in range(len(self.fields)):
208 field = definition[idx]
209 p = self.fields[idx]
210 if field[1] != p.name:
211 return False
212 if field[0] != p.type.name:
213 raise ParseError(
214 "Unexpected field type `%s' (should be `%s'), "
215 "while parsing msg/def/field `%s/%s/%s'" %
216 (field[0], p.type, p.name, definition, field))
217 return True
218
219
Klement Sekera958b7502017-09-28 06:31:53 +0200220class JsonParser(object):
Klement Sekera8f2a4ea2017-05-04 06:15:18 +0200221 def __init__(self, logger, files, simple_type_class=SimpleType,
222 struct_type_class=StructType, field_class=Field,
223 message_class=Message):
224 self.messages = {}
225 self.types = {
226 x: simple_type_class(x) for x in [
227 'i8', 'i16', 'i32', 'i64',
228 'u8', 'u16', 'u32', 'u64',
229 'f64'
230 ]
231 }
232
233 self.simple_type_class = simple_type_class
234 self.struct_type_class = struct_type_class
235 self.field_class = field_class
236 self.message_class = message_class
237
238 self.exceptions = []
239 self.json_files = []
240 self.types_by_json = {}
241 self.messages_by_json = {}
242 self.logger = logger
243 for f in files:
244 self.parse_json_file(f)
245 self.finalize_parsing()
246
247 def parse_json_file(self, path):
248 self.logger.info("Parsing json api file: `%s'" % path)
249 self.json_files.append(path)
250 self.types_by_json[path] = {}
251 self.messages_by_json[path] = {}
252 with open(path) as f:
253 j = json.load(f)
254 for t in j['types']:
255 try:
256 type_ = self.struct_type_class(t, self.types,
257 self.field_class)
258 if type_.name in self.types:
259 raise ParseError("Duplicate type `%s'" % type_.name)
260 except ParseError as e:
261 self.exceptions.append(e)
262 continue
263 self.types[type_.name] = type_
264 self.types_by_json[path][type_.name] = type_
265 for m in j['messages']:
266 try:
267 msg = self.message_class(self.logger, m, self.types,
268 self.struct_type_class,
269 self.simple_type_class,
270 self.field_class)
271 if msg.name in self.messages:
272 raise ParseError("Duplicate message `%s'" % msg.name)
273 except ParseError as e:
274 self.exceptions.append(e)
275 continue
276 self.messages[msg.name] = msg
277 self.messages_by_json[path][msg.name] = msg
278
279 def get_reply(self, message):
280 if self.messages[message].is_dump():
281 return self.messages["%s_details" % message[:-len("_dump")]]
282 return self.messages["%s_reply" % message]
283
284 def finalize_parsing(self):
285 if len(self.messages) == 0:
286 for e in self.exceptions:
287 self.logger.error(e)
288 raise Exception("No messages parsed.")
289 for jn, j in self.messages_by_json.items():
290 remove = []
291 for n, m in j.items():
292 try:
293 if not m.is_reply():
294 try:
295 m.reply = self.get_reply(n)
Klement Sekeradc15be22017-06-12 06:49:33 +0200296 m.reply.request = m
Klement Sekera8f2a4ea2017-05-04 06:15:18 +0200297 except:
298 raise ParseError(
299 "Cannot find reply to message `%s'" % n)
300 except ParseError as e:
301 self.exceptions.append(e)
302 remove.append(n)
303
304 self.messages_by_json[jn] = {
305 k: v for k, v in j.items() if k not in remove}