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