]> git.proxmox.com Git - mirror_ovs.git/blob - build-aux/extract-ofp-errors
Merge branch 'dpdk_merge' of https://github.com/istokes/ovs into HEAD
[mirror_ovs.git] / build-aux / extract-ofp-errors
1 #! /usr/bin/python
2
3 import sys
4 import os.path
5 import re
6
7 macros = {}
8
9 # Map from OpenFlow version number to version ID used in ofp_header.
10 version_map = {"1.0": 0x01,
11 "1.1": 0x02,
12 "1.2": 0x03,
13 "1.3": 0x04,
14 "1.4": 0x05,
15 "1.5": 0x06,
16 "1.6": 0x07}
17 version_reverse_map = dict((v, k) for (k, v) in version_map.items())
18
19 token = None
20 line = ""
21 idRe = "[a-zA-Z_][a-zA-Z_0-9]*"
22 tokenRe = "#?" + idRe + "|[0-9]+|."
23 inComment = False
24 inDirective = False
25
26 def open_file(fn):
27 global fileName
28 global inputFile
29 global lineNumber
30 fileName = fn
31 inputFile = open(fileName)
32 lineNumber = 0
33
34 def tryGetLine():
35 global inputFile
36 global line
37 global lineNumber
38 line = inputFile.readline()
39 lineNumber += 1
40 return line != ""
41
42 def getLine():
43 if not tryGetLine():
44 fatal("unexpected end of input")
45
46 def getToken():
47 global token
48 global line
49 global inComment
50 global inDirective
51 while True:
52 line = line.lstrip()
53 if line != "":
54 if line.startswith("/*"):
55 inComment = True
56 line = line[2:]
57 elif inComment:
58 commentEnd = line.find("*/")
59 if commentEnd < 0:
60 line = ""
61 else:
62 inComment = False
63 line = line[commentEnd + 2:]
64 else:
65 match = re.match(tokenRe, line)
66 token = match.group(0)
67 line = line[len(token):]
68 if token.startswith('#'):
69 inDirective = True
70 elif token in macros and not inDirective:
71 line = macros[token] + line
72 continue
73 return True
74 elif inDirective:
75 token = "$"
76 inDirective = False
77 return True
78 else:
79 global lineNumber
80 line = inputFile.readline()
81 lineNumber += 1
82 while line.endswith("\\\n"):
83 line = line[:-2] + inputFile.readline()
84 lineNumber += 1
85 if line == "":
86 if token == None:
87 fatal("unexpected end of input")
88 token = None
89 return False
90
91 n_errors = 0
92 def error(msg):
93 global n_errors
94 sys.stderr.write("%s:%d: %s\n" % (fileName, lineNumber, msg))
95 n_errors += 1
96
97 def fatal(msg):
98 error(msg)
99 sys.exit(1)
100
101 def skipDirective():
102 getToken()
103 while token != '$':
104 getToken()
105
106 def isId(s):
107 return re.match(idRe + "$", s) != None
108
109 def forceId():
110 if not isId(token):
111 fatal("identifier expected")
112
113 def forceInteger():
114 if not re.match('[0-9]+$', token):
115 fatal("integer expected")
116
117 def match(t):
118 if token == t:
119 getToken()
120 return True
121 else:
122 return False
123
124 def forceMatch(t):
125 if not match(t):
126 fatal("%s expected" % t)
127
128 def parseTaggedName():
129 assert token in ('struct', 'union')
130 name = token
131 getToken()
132 forceId()
133 name = "%s %s" % (name, token)
134 getToken()
135 return name
136
137 def print_enum(tag, constants, storage_class):
138 print ("""
139 %(storage_class)sconst char *
140 %(tag)s_to_string(uint16_t value)
141 {
142 switch (value) {\
143 """ % {"tag": tag,
144 "bufferlen": len(tag) + 32,
145 "storage_class": storage_class})
146 for constant in constants:
147 print (" case %s: return \"%s\";" % (constant, constant))
148 print ("""\
149 }
150 return NULL;
151 }\
152 """ % {"tag": tag})
153
154 def usage():
155 argv0 = os.path.basename(sys.argv[0])
156 print ('''\
157 %(argv0)s, for extracting OpenFlow error codes from header files
158 usage: %(argv0)s ERROR_HEADER VENDOR_HEADER
159
160 This program reads VENDOR_HEADER to obtain OpenFlow vendor (aka
161 experimenter IDs), then ERROR_HEADER to obtain OpenFlow error number.
162 It outputs a C source file for translating OpenFlow error codes into
163 strings.
164
165 ERROR_HEADER should point to include/openvswitch/ofp-errors.h.
166 VENDOR_HEADER should point to include/openflow/openflow-common.h.
167 The output is suitable for use as lib/ofp-errors.inc.\
168 ''' % {"argv0": argv0})
169 sys.exit(0)
170
171 def extract_vendor_ids(fn):
172 global vendor_map
173 vendor_map = {}
174 vendor_loc = {}
175
176 open_file(fn)
177 while tryGetLine():
178 m = re.match(r'#define\s+([A-Z0-9_]+)_VENDOR_ID\s+(0x[0-9a-fA-F]+|[0-9]+)', line)
179 if not m:
180 continue
181
182 name = m.group(1)
183 id_ = int(m.group(2), 0)
184
185 if name in vendor_map:
186 error("%s: duplicate definition of vendor" % name)
187 sys.stderr.write("%s: Here is the location of the previous "
188 "definition.\n" % vendor_loc[name])
189 sys.exit(1)
190
191 vendor_map[name] = id_
192 vendor_loc[name] = "%s:%d" % (fileName, lineNumber)
193
194 if not vendor_map:
195 fatal("%s: no vendor definitions found" % fn)
196
197 inputFile.close()
198
199 vendor_reverse_map = {}
200 for name, id_ in vendor_map.items():
201 if id_ in vendor_reverse_map:
202 fatal("%s: duplicate vendor id for vendors %s and %s"
203 % (id_, vendor_reverse_map[id_], name))
204 vendor_reverse_map[id_] = name
205
206 def extract_ofp_errors(fn):
207 error_types = {}
208
209 comments = []
210 names = []
211 domain = {}
212 reverse = {}
213 for domain_name in version_map.values():
214 domain[domain_name] = {}
215 reverse[domain_name] = {}
216
217 n_errors = 0
218 expected_errors = {}
219
220 open_file(fn)
221
222 while True:
223 getLine()
224 if re.match('enum ofperr', line):
225 break
226
227 while True:
228 getLine()
229 if line.startswith('/*') or not line or line.isspace():
230 continue
231 elif re.match('}', line):
232 break
233
234 if not line.lstrip().startswith('/*'):
235 fatal("unexpected syntax between errors")
236
237 comment = line.lstrip()[2:].strip()
238 while not comment.endswith('*/'):
239 getLine()
240 if line.startswith('/*') or not line or line.isspace():
241 fatal("unexpected syntax within error")
242 comment += ' %s' % line.lstrip('* \t').rstrip(' \t\r\n')
243 comment = comment[:-2].rstrip()
244
245 m = re.match('Expected: (.*)\.$', comment)
246 if m:
247 expected_errors[m.group(1)] = (fileName, lineNumber)
248 continue
249
250 m = re.match('((?:.(?!\. ))+.)\.\s+(.*)$', comment)
251 if not m:
252 fatal("unexpected syntax between errors")
253
254 dsts, comment = m.groups()
255
256 getLine()
257 m = re.match('\s+(?:OFPERR_([A-Z0-9_]+))(\s*=\s*OFPERR_OFS)?,',
258 line)
259 if not m:
260 fatal("syntax error expecting OFPERR_ enum value")
261
262 enum = m.group(1)
263 if enum in names:
264 fatal("%s specified twice" % enum)
265
266 comments.append(re.sub('\[[^]]*\]', '', comment))
267 names.append(enum)
268
269 for dst in dsts.split(', '):
270 m = re.match(r'([A-Z]+)([0-9.]+)(\+|-[0-9.]+)?\((\d+)(?:,(\d+))?\)$', dst)
271 if not m:
272 fatal("%r: syntax error in destination" % dst)
273 vendor_name = m.group(1)
274 version1_name = m.group(2)
275 version2_name = m.group(3)
276 type_ = int(m.group(4))
277 if m.group(5):
278 code = int(m.group(5))
279 else:
280 code = None
281
282 if vendor_name not in vendor_map:
283 fatal("%s: unknown vendor" % vendor_name)
284 vendor = vendor_map[vendor_name]
285
286 if version1_name not in version_map:
287 fatal("%s: unknown OpenFlow version" % version1_name)
288 v1 = version_map[version1_name]
289
290 if version2_name is None:
291 v2 = v1
292 elif version2_name == "+":
293 v2 = max(version_map.values())
294 elif version2_name[1:] not in version_map:
295 fatal("%s: unknown OpenFlow version" % version2_name[1:])
296 else:
297 v2 = version_map[version2_name[1:]]
298
299 if v2 < v1:
300 fatal("%s%s: %s precedes %s"
301 % (version1_name, version2_name,
302 version2_name, version1_name))
303
304 if vendor == vendor_map['OF']:
305 # All standard OpenFlow errors have a type and a code.
306 if code is None:
307 fatal("%s: %s domain requires code" % (dst, vendor_name))
308 elif vendor == vendor_map['NX']:
309 # Before OpenFlow 1.2, OVS used a Nicira extension to
310 # define errors that included a type and a code.
311 #
312 # In OpenFlow 1.2 and later, Nicira extension errors
313 # are defined using the OpenFlow experimenter error
314 # mechanism that includes a type but not a code.
315 if v1 < version_map['1.2'] or v2 < version_map['1.2']:
316 if code is None:
317 fatal("%s: NX1.0 and NX1.1 domains require code"
318 % (dst, vendor_name))
319 if v1 >= version_map['1.2'] or v2 >= version_map['1.2']:
320 if code is not None:
321 fatal("%s: NX1.2+ domains do not have codes" % dst)
322 else:
323 # Experimenter extension error for OF1.2+ only.
324 if v1 < version_map['1.2']:
325 fatal("%s: %s domain not supported before OF1.2"
326 % (dst, vendor_name))
327 if code is not None:
328 fatal("%s: %s domains do not have codes"
329 % (dst, vendor_name))
330 if code is None:
331 code = 0
332
333 for version in range(v1, v2 + 1):
334 domain[version].setdefault(vendor, {})
335 domain[version][vendor].setdefault(type_, {})
336 if code in domain[version][vendor][type_]:
337 msg = "%#x,%d,%d in OF%s means both %s and %s" % (
338 vendor, type_, code, version_reverse_map[version],
339 domain[version][vendor][type_][code][0], enum)
340 if msg in expected_errors:
341 del expected_errors[msg]
342 else:
343 error("%s: %s." % (dst, msg))
344 sys.stderr.write("%s:%d: %s: Here is the location "
345 "of the previous definition.\n"
346 % (domain[version][vendor][type_][code][1],
347 domain[version][vendor][type_][code][2],
348 dst))
349 else:
350 domain[version][vendor][type_][code] = (enum, fileName,
351 lineNumber)
352
353 assert enum not in reverse[version]
354 reverse[version][enum] = (vendor, type_, code)
355
356 inputFile.close()
357
358 for fn, ln in expected_errors.values():
359 sys.stderr.write("%s:%d: expected duplicate not used.\n" % (fn, ln))
360 n_errors += 1
361
362 if n_errors:
363 sys.exit(1)
364
365 print ("""\
366 /* Generated automatically; do not modify! -*- buffer-read-only: t -*- */
367
368 #define OFPERR_N_ERRORS %d
369
370 struct ofperr_domain {
371 const char *name;
372 uint8_t version;
373 enum ofperr (*decode)(uint32_t vendor, uint16_t type, uint16_t code);
374 struct triplet errors[OFPERR_N_ERRORS];
375 };
376
377 static const char *error_names[OFPERR_N_ERRORS] = {
378 %s
379 };
380
381 static const char *error_comments[OFPERR_N_ERRORS] = {
382 %s
383 };\
384 """ % (len(names),
385 '\n'.join(' "%s",' % name for name in names),
386 '\n'.join(' "%s",' % re.sub(r'(["\\])', r'\\\1', comment)
387 for comment in comments)))
388
389 def output_domain(map, name, description, version):
390 print ("""
391 static enum ofperr
392 %s_decode(uint32_t vendor, uint16_t type, uint16_t code)
393 {
394 switch (((uint64_t) vendor << 32) | (uint32_t) (type << 16) | code) {"""
395 % name)
396 found = set()
397 for enum in names:
398 if enum not in map:
399 continue
400 vendor, type_, code = map[enum]
401 value = (vendor << 32) | (type_ << 16) | code
402 if value in found:
403 continue
404 found.add(value)
405 if vendor:
406 vendor_s = "(%#xULL << 32) | " % vendor
407 else:
408 vendor_s = ""
409 print (" case %s(uint32_t) (%d << 16) | %d:" % (vendor_s,
410 type_, code))
411 print (" return OFPERR_%s;" % enum)
412 print ("""\
413 }
414
415 return 0;
416 }""")
417
418 print ("""
419 static const struct ofperr_domain %s = {
420 "%s",
421 %d,
422 %s_decode,
423 {""" % (name, description, version, name))
424 for enum in names:
425 if enum in map:
426 vendor, type_, code = map[enum]
427 if code == None:
428 code = -1
429 print (" { %#8x, %2d, %3d }, /* %s */" % (vendor, type_, code, enum))
430 else:
431 print (" { -1, -1, -1 }, /* %s */" % enum)
432 print ("""\
433 },
434 };""")
435
436 for version_name, id_ in version_map.items():
437 var = 'ofperr_of' + re.sub('[^A-Za-z0-9_]', '', version_name)
438 description = "OpenFlow %s" % version_name
439 output_domain(reverse[id_], var, description, id_)
440
441 if __name__ == '__main__':
442 if '--help' in sys.argv:
443 usage()
444 elif len(sys.argv) != 3:
445 sys.stderr.write("exactly two non-options arguments required; "
446 "use --help for help\n")
447 sys.exit(1)
448 else:
449 extract_vendor_ids(sys.argv[2])
450 extract_ofp_errors(sys.argv[1])