]>
Commit | Line | Data |
---|---|---|
dc4762ed BP |
1 | #! /usr/bin/python |
2 | ||
3 | import sys | |
4 | import os.path | |
5 | import re | |
6 | ||
7 | macros = {} | |
8 | ||
514887ee BP |
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, | |
c37c0382 | 13 | "1.3": 0x04, |
42dccab5 | 14 | "1.4": 0x05, |
b79d45a1 BP |
15 | "1.5": 0x06, |
16 | "1.6": 0x07} | |
4ab66562 | 17 | version_reverse_map = dict((v, k) for (k, v) in version_map.items()) |
514887ee | 18 | |
dc4762ed BP |
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 | |
90bf1e07 | 25 | |
514887ee BP |
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 | |
90bf1e07 BP |
36 | global line |
37 | global lineNumber | |
38 | line = inputFile.readline() | |
39 | lineNumber += 1 | |
514887ee BP |
40 | return line != "" |
41 | ||
42 | def getLine(): | |
43 | if not tryGetLine(): | |
90bf1e07 BP |
44 | fatal("unexpected end of input") |
45 | ||
dc4762ed BP |
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 | ||
2e0525bc SH |
91 | n_errors = 0 |
92 | def error(msg): | |
93 | global n_errors | |
90bf1e07 | 94 | sys.stderr.write("%s:%d: %s\n" % (fileName, lineNumber, msg)) |
2e0525bc SH |
95 | n_errors += 1 |
96 | ||
97 | def fatal(msg): | |
98 | error(msg) | |
dc4762ed BP |
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): | |
f4d90e0d | 138 | print (""" |
dc4762ed BP |
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, | |
f4d90e0d | 145 | "storage_class": storage_class}) |
dc4762ed | 146 | for constant in constants: |
f4d90e0d DM |
147 | print (" case %s: return \"%s\";" % (constant, constant)) |
148 | print ("""\ | |
dc4762ed BP |
149 | } |
150 | return NULL; | |
151 | }\ | |
f4d90e0d | 152 | """ % {"tag": tag}) |
dc4762ed BP |
153 | |
154 | def usage(): | |
155 | argv0 = os.path.basename(sys.argv[0]) | |
f4d90e0d | 156 | print ('''\ |
dc4762ed | 157 | %(argv0)s, for extracting OpenFlow error codes from header files |
514887ee | 158 | usage: %(argv0)s ERROR_HEADER VENDOR_HEADER |
dc4762ed | 159 | |
514887ee BP |
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. | |
dc4762ed | 164 | |
e03c096d | 165 | ERROR_HEADER should point to include/openvswitch/ofp-errors.h. |
514887ee BP |
166 | VENDOR_HEADER should point to include/openflow/openflow-common.h. |
167 | The output is suitable for use as lib/ofp-errors.inc.\ | |
f4d90e0d | 168 | ''' % {"argv0": argv0}) |
dc4762ed BP |
169 | sys.exit(0) |
170 | ||
514887ee BP |
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 | |
83ed4519 | 205 | |
514887ee | 206 | def extract_ofp_errors(fn): |
dc4762ed BP |
207 | error_types = {} |
208 | ||
90bf1e07 BP |
209 | comments = [] |
210 | names = [] | |
211 | domain = {} | |
212 | reverse = {} | |
514887ee | 213 | for domain_name in version_map.values(): |
90bf1e07 BP |
214 | domain[domain_name] = {} |
215 | reverse[domain_name] = {} | |
216 | ||
2e0525bc SH |
217 | n_errors = 0 |
218 | expected_errors = {} | |
219 | ||
514887ee | 220 | open_file(fn) |
90bf1e07 | 221 | |
514887ee BP |
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") | |
90bf1e07 | 236 | |
514887ee BP |
237 | comment = line.lstrip()[2:].strip() |
238 | while not comment.endswith('*/'): | |
90bf1e07 BP |
239 | getLine() |
240 | if line.startswith('/*') or not line or line.isspace(): | |
514887ee BP |
241 | fatal("unexpected syntax within error") |
242 | comment += ' %s' % line.lstrip('* \t').rstrip(' \t\r\n') | |
243 | comment = comment[:-2].rstrip() | |
2e0525bc | 244 | |
514887ee BP |
245 | m = re.match('Expected: (.*)\.$', comment) |
246 | if m: | |
247 | expected_errors[m.group(1)] = (fileName, lineNumber) | |
248 | continue | |
2e0525bc | 249 | |
d4b5b1bc | 250 | m = re.match('((?:.(?!\. ))+.)\.\s+(.*)$', comment) |
514887ee BP |
251 | if not m: |
252 | fatal("unexpected syntax between errors") | |
2e0525bc | 253 | |
514887ee | 254 | dsts, comment = m.groups() |
90bf1e07 | 255 | |
514887ee BP |
256 | getLine() |
257 | m = re.match('\s+(?:OFPERR_([A-Z0-9_]+))(\s*=\s*OFPERR_OFS)?,', | |
258 | line) | |
259 | if not m: | |
d4b5b1bc | 260 | fatal("syntax error expecting OFPERR_ enum value") |
90bf1e07 | 261 | |
514887ee BP |
262 | enum = m.group(1) |
263 | if enum in names: | |
264 | fatal("%s specified twice" % enum) | |
90bf1e07 | 265 | |
514887ee BP |
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 | ||
d12f128a BP |
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)) | |
514887ee BP |
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) | |
d12f128a BP |
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)) | |
514887ee | 327 | if code is not None: |
d12f128a BP |
328 | fatal("%s: %s domains do not have codes" |
329 | % (dst, vendor_name)) | |
330 | if code is None: | |
331 | code = 0 | |
514887ee BP |
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] | |
2e0525bc | 342 | else: |
514887ee BP |
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) | |
2e0525bc | 352 | |
514887ee BP |
353 | assert enum not in reverse[version] |
354 | reverse[version][enum] = (vendor, type_, code) | |
90bf1e07 | 355 | |
514887ee | 356 | inputFile.close() |
dc4762ed | 357 | |
f4d90e0d | 358 | for fn, ln in expected_errors.values(): |
2e0525bc SH |
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 | ||
f4d90e0d | 365 | print ("""\ |
90bf1e07 BP |
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; | |
514887ee BP |
373 | enum ofperr (*decode)(uint32_t vendor, uint16_t type, uint16_t code); |
374 | struct triplet errors[OFPERR_N_ERRORS]; | |
90bf1e07 BP |
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) | |
f4d90e0d | 387 | for comment in comments))) |
90bf1e07 BP |
388 | |
389 | def output_domain(map, name, description, version): | |
f4d90e0d | 390 | print (""" |
90bf1e07 | 391 | static enum ofperr |
514887ee | 392 | %s_decode(uint32_t vendor, uint16_t type, uint16_t code) |
90bf1e07 | 393 | { |
35368d50 BP |
394 | switch (((uint64_t) vendor << 32) | (uint32_t) (type << 16) | code) {""" |
395 | % name) | |
2e0525bc | 396 | found = set() |
90bf1e07 BP |
397 | for enum in names: |
398 | if enum not in map: | |
399 | continue | |
514887ee | 400 | vendor, type_, code = map[enum] |
514887ee | 401 | value = (vendor << 32) | (type_ << 16) | code |
2e0525bc SH |
402 | if value in found: |
403 | continue | |
404 | found.add(value) | |
514887ee BP |
405 | if vendor: |
406 | vendor_s = "(%#xULL << 32) | " % vendor | |
407 | else: | |
408 | vendor_s = "" | |
35368d50 BP |
409 | print (" case %s(uint32_t) (%d << 16) | %d:" % (vendor_s, |
410 | type_, code)) | |
f4d90e0d DM |
411 | print (" return OFPERR_%s;" % enum) |
412 | print ("""\ | |
dc4762ed | 413 | } |
90bf1e07 | 414 | |
90bf1e07 | 415 | return 0; |
f4d90e0d | 416 | }""") |
90bf1e07 | 417 | |
f4d90e0d | 418 | print (""" |
688e86e1 | 419 | static const struct ofperr_domain %s = { |
90bf1e07 BP |
420 | "%s", |
421 | %d, | |
422 | %s_decode, | |
f4d90e0d | 423 | {""" % (name, description, version, name)) |
90bf1e07 BP |
424 | for enum in names: |
425 | if enum in map: | |
514887ee | 426 | vendor, type_, code = map[enum] |
90bf1e07 BP |
427 | if code == None: |
428 | code = -1 | |
d34a1cc0 | 429 | print (" { %#8x, %2d, %3d }, /* %s */" % (vendor, type_, code, enum)) |
90bf1e07 | 430 | else: |
514887ee | 431 | print (" { -1, -1, -1 }, /* %s */" % enum) |
f4d90e0d | 432 | print ("""\ |
90bf1e07 | 433 | }, |
f4d90e0d | 434 | };""") |
90bf1e07 | 435 | |
514887ee BP |
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_) | |
dc4762ed BP |
440 | |
441 | if __name__ == '__main__': | |
442 | if '--help' in sys.argv: | |
443 | usage() | |
514887ee BP |
444 | elif len(sys.argv) != 3: |
445 | sys.stderr.write("exactly two non-options arguments required; " | |
dc4762ed BP |
446 | "use --help for help\n") |
447 | sys.exit(1) | |
448 | else: | |
514887ee BP |
449 | extract_vendor_ids(sys.argv[2]) |
450 | extract_ofp_errors(sys.argv[1]) |