]>
Commit | Line | Data |
---|---|---|
c2d936a4 BP |
1 | #! /usr/bin/python |
2 | ||
3 | import sys | |
4 | import os.path | |
5 | import re | |
6 | ||
7 | OFP_ACTION_ALIGN = 8 | |
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} | |
4ab66562 | 16 | version_reverse_map = dict((v, k) for (k, v) in version_map.items()) |
c2d936a4 BP |
17 | |
18 | # Map from vendor name to the length of the action header. | |
19 | vendor_map = {"OF": (0x00000000, 4), | |
914624f8 | 20 | "ONF": (0x4f4e4600, 10), |
c2d936a4 BP |
21 | "NX": (0x00002320, 10)} |
22 | ||
23 | # Basic types used in action arguments. | |
24 | types = {} | |
25 | types['uint8_t'] = {"size": 1, "align": 1, "ntoh": None, "hton": None} | |
26 | types['ovs_be16'] = {"size": 2, "align": 2, "ntoh": "ntohs", "hton": "htons"} | |
27 | types['ovs_be32'] = {"size": 4, "align": 4, "ntoh": "ntohl", "hton": "htonl"} | |
28 | types['ovs_be64'] = {"size": 8, "align": 8, "ntoh": "ntohll", "hton": "htonll"} | |
29 | types['uint16_t'] = {"size": 2, "align": 2, "ntoh": None, "hton": None} | |
30 | types['uint32_t'] = {"size": 4, "align": 4, "ntoh": None, "hton": None} | |
31 | types['uint64_t'] = {"size": 8, "align": 8, "ntoh": None, "hton": None} | |
32 | ||
33 | line = "" | |
34 | ||
35 | arg_structs = set() | |
36 | ||
37 | def round_up(x, y): | |
3fa5aa42 | 38 | return int((x + (y - 1)) / y) * y |
c2d936a4 BP |
39 | |
40 | def open_file(fn): | |
41 | global file_name | |
42 | global input_file | |
43 | global line_number | |
44 | file_name = fn | |
45 | input_file = open(file_name) | |
46 | line_number = 0 | |
47 | ||
48 | def get_line(): | |
49 | global input_file | |
50 | global line | |
51 | global line_number | |
52 | line = input_file.readline() | |
53 | line_number += 1 | |
54 | if line == "": | |
55 | fatal("unexpected end of input") | |
56 | return line | |
57 | ||
58 | n_errors = 0 | |
59 | def error(msg): | |
60 | global n_errors | |
61 | sys.stderr.write("%s:%d: %s\n" % (file_name, line_number, msg)) | |
62 | n_errors += 1 | |
63 | ||
64 | def fatal(msg): | |
65 | error(msg) | |
66 | sys.exit(1) | |
67 | ||
68 | def usage(): | |
69 | argv0 = os.path.basename(sys.argv[0]) | |
d34a1cc0 | 70 | print('''\ |
c2d936a4 BP |
71 | %(argv0)s, for extracting OpenFlow action data |
72 | usage: %(argv0)s OFP_ACTIONS.C [--prototypes | --definitions] | |
73 | ||
74 | This program reads ofp-actions.c to obtain information about OpenFlow | |
75 | actions. With --prototypes, it outputs on stdout a set of prototypes to | |
76 | #include early in ofp-actions.c. With --definitions, it outputs on stdout | |
77 | a set of definitions to #include late in ofp-actions.c | |
78 | ||
79 | OFP_ACTIONS.C should point to lib/ofp-actions.c.\ | |
80 | ''' % {"argv0": argv0}) | |
81 | sys.exit(0) | |
82 | ||
83 | def extract_ofp_actions(fn, definitions): | |
84 | error_types = {} | |
85 | ||
86 | comments = [] | |
87 | names = [] | |
88 | domain = {} | |
89 | for code, size in vendor_map.values(): | |
90 | domain[code] = {} | |
91 | enums = {} | |
92 | ||
93 | n_errors = 0 | |
94 | ||
95 | open_file(fn) | |
96 | ||
97 | while True: | |
98 | get_line() | |
99 | if re.match('enum ofp_raw_action_type {', line): | |
100 | break | |
101 | ||
102 | while True: | |
103 | get_line() | |
104 | if line.startswith('/*') or not line or line.isspace(): | |
105 | continue | |
106 | elif re.match('}', line): | |
107 | break | |
108 | ||
109 | if not line.lstrip().startswith('/*'): | |
110 | fatal("unexpected syntax between actions") | |
111 | ||
112 | comment = line.lstrip()[2:].strip() | |
113 | while not comment.endswith('*/'): | |
114 | get_line() | |
115 | if line.startswith('/*') or not line or line.isspace(): | |
116 | fatal("unexpected syntax within action") | |
117 | comment += ' %s' % line.lstrip('* \t').rstrip(' \t\r\n') | |
118 | comment = re.sub('\[[^]]*\]', '', comment) | |
119 | comment = comment[:-2].rstrip() | |
120 | ||
121 | m = re.match('([^:]+):\s+(.*)$', comment) | |
122 | if not m: | |
123 | fatal("unexpected syntax between actions") | |
124 | ||
125 | dsts = m.group(1) | |
04f48a68 YHW |
126 | argtypes = m.group(2).strip().replace('.', '', 1) |
127 | ||
128 | if 'VLMFF' in argtypes: | |
129 | arg_vl_mff_map = True | |
130 | else: | |
131 | arg_vl_mff_map = False | |
132 | argtype = argtypes.replace('VLMFF', '', 1).rstrip() | |
c2d936a4 BP |
133 | |
134 | get_line() | |
135 | m = re.match(r'\s+(([A-Z]+)_RAW([0-9]*)_([A-Z0-9_]+)),?', line) | |
136 | if not m: | |
137 | fatal("syntax error expecting enum value") | |
138 | ||
139 | enum = m.group(1) | |
140 | if enum in names: | |
141 | fatal("%s specified twice" % enum) | |
142 | ||
143 | names.append(enum) | |
144 | ||
145 | for dst in dsts.split(', '): | |
146 | m = re.match(r'([A-Z]+)([0-9.]+)(\+|-[0-9.]+)?(?:\((\d+)\))(?: is deprecated \(([^)]+)\))?$', dst) | |
147 | if not m: | |
148 | fatal("%r: syntax error in destination" % dst) | |
149 | vendor_name = m.group(1) | |
150 | version1_name = m.group(2) | |
151 | version2_name = m.group(3) | |
152 | type_ = int(m.group(4)) | |
153 | deprecation = m.group(5) | |
154 | ||
155 | if vendor_name not in vendor_map: | |
156 | fatal("%s: unknown vendor" % vendor_name) | |
157 | vendor = vendor_map[vendor_name][0] | |
158 | ||
159 | if version1_name not in version_map: | |
160 | fatal("%s: unknown OpenFlow version" % version1_name) | |
161 | v1 = version_map[version1_name] | |
162 | ||
163 | if version2_name is None: | |
164 | v2 = v1 | |
165 | elif version2_name == "+": | |
166 | v2 = max(version_map.values()) | |
167 | elif version2_name[1:] not in version_map: | |
168 | fatal("%s: unknown OpenFlow version" % version2_name[1:]) | |
169 | else: | |
170 | v2 = version_map[version2_name[1:]] | |
171 | ||
172 | if v2 < v1: | |
173 | fatal("%s%s: %s precedes %s" | |
174 | % (version1_name, version2_name, | |
175 | version2_name, version1_name)) | |
176 | ||
177 | for version in range(v1, v2 + 1): | |
178 | domain[vendor].setdefault(type_, {}) | |
179 | if version in domain[vendor][type_]: | |
180 | v = domain[vendor][type_][version] | |
181 | msg = "%#x,%d in OF%s means both %s and %s" % ( | |
182 | vendor, type_, version_reverse_map[version], | |
183 | v["enum"], enum) | |
184 | error("%s: %s." % (dst, msg)) | |
185 | sys.stderr.write("%s:%d: %s: Here is the location " | |
186 | "of the previous definition.\n" | |
187 | % (v["file_name"], v["line_number"], | |
188 | dst)) | |
189 | n_errors += 1 | |
190 | else: | |
191 | header_len = vendor_map[vendor_name][1] | |
192 | ||
193 | base_argtype = argtype.replace(', ..', '', 1) | |
194 | if base_argtype in types: | |
195 | arg_align = types[base_argtype]['align'] | |
196 | arg_len = types[base_argtype]['size'] | |
197 | arg_ofs = round_up(header_len, arg_align) | |
198 | min_length = round_up(arg_ofs + arg_len, | |
199 | OFP_ACTION_ALIGN) | |
200 | elif base_argtype == 'void': | |
201 | min_length = round_up(header_len, OFP_ACTION_ALIGN) | |
202 | arg_len = 0 | |
203 | arg_ofs = 0 | |
204 | elif re.match(r'struct [a-zA-Z0-9_]+$', base_argtype): | |
205 | min_length = 'sizeof(%s)' % base_argtype | |
206 | arg_structs.add(base_argtype) | |
207 | arg_len = 0 | |
208 | arg_ofs = 0 | |
209 | # should also emit OFP_ACTION_ALIGN assertion | |
210 | else: | |
211 | fatal("bad argument type %s" % argtype) | |
212 | ||
213 | ellipsis = argtype != base_argtype | |
214 | if ellipsis: | |
215 | max_length = '65536 - OFP_ACTION_ALIGN' | |
216 | else: | |
217 | max_length = min_length | |
218 | ||
04f48a68 YHW |
219 | info = {"enum": enum, # 0 |
220 | "deprecation": deprecation, # 1 | |
221 | "file_name": file_name, # 2 | |
222 | "line_number": line_number, # 3 | |
223 | "min_length": min_length, # 4 | |
224 | "max_length": max_length, # 5 | |
225 | "arg_ofs": arg_ofs, # 6 | |
226 | "arg_len": arg_len, # 7 | |
227 | "base_argtype": base_argtype, # 8 | |
228 | "arg_vl_mff_map": arg_vl_mff_map, # 9 | |
229 | "version": version, # 10 | |
230 | "type": type_} # 11 | |
c2d936a4 BP |
231 | domain[vendor][type_][version] = info |
232 | ||
233 | enums.setdefault(enum, []) | |
234 | enums[enum].append(info) | |
235 | ||
236 | input_file.close() | |
237 | ||
238 | if n_errors: | |
239 | sys.exit(1) | |
240 | ||
d34a1cc0 | 241 | print("""\ |
c2d936a4 | 242 | /* Generated automatically; do not modify! -*- buffer-read-only: t -*- */ |
d34a1cc0 | 243 | """) |
c2d936a4 BP |
244 | |
245 | if definitions: | |
d34a1cc0 | 246 | print("/* Verify that structs used as actions are reasonable sizes. */") |
c2d936a4 | 247 | for s in sorted(arg_structs): |
d34a1cc0 | 248 | print("BUILD_ASSERT_DECL(sizeof(%s) %% OFP_ACTION_ALIGN == 0);" % s) |
c2d936a4 | 249 | |
d34a1cc0 | 250 | print("\nstatic struct ofpact_raw_instance all_raw_instances[] = {") |
c2d936a4 BP |
251 | for vendor in domain: |
252 | for type_ in domain[vendor]: | |
253 | for version in domain[vendor][type_]: | |
254 | d = domain[vendor][type_][version] | |
d34a1cc0 JW |
255 | print(" { { 0x%08x, %2d, 0x%02x }, " % ( |
256 | vendor, type_, version)) | |
257 | print(" %s," % d["enum"]) | |
258 | print(" HMAP_NODE_NULL_INITIALIZER,") | |
259 | print(" HMAP_NODE_NULL_INITIALIZER,") | |
260 | print(" %s," % d["min_length"]) | |
261 | print(" %s," % d["max_length"]) | |
262 | print(" %s," % d["arg_ofs"]) | |
263 | print(" %s," % d["arg_len"]) | |
264 | print(" \"%s\"," % re.sub('_RAW[0-9]*', '', d["enum"], 1)) | |
c2d936a4 | 265 | if d["deprecation"]: |
d34a1cc0 | 266 | print(" \"%s\"," % re.sub(r'(["\\])', r'\\\1', d["deprecation"])) |
c2d936a4 | 267 | else: |
d34a1cc0 JW |
268 | print(" NULL,") |
269 | print(" },") | |
270 | print("};") | |
c2d936a4 BP |
271 | |
272 | for versions in enums.values(): | |
273 | need_ofp_version = False | |
274 | for v in versions: | |
275 | assert v["arg_len"] == versions[0]["arg_len"] | |
276 | assert v["base_argtype"] == versions[0]["base_argtype"] | |
277 | if (v["min_length"] != versions[0]["min_length"] or | |
278 | v["arg_ofs"] != versions[0]["arg_ofs"] or | |
279 | v["type"] != versions[0]["type"]): | |
280 | need_ofp_version = True | |
281 | base_argtype = versions[0]["base_argtype"] | |
282 | ||
283 | decl = "static inline " | |
284 | if base_argtype.startswith('struct'): | |
285 | decl += "%s *" %base_argtype | |
286 | else: | |
287 | decl += "void" | |
288 | decl += "\nput_%s(struct ofpbuf *openflow" % versions[0]["enum"].replace('_RAW', '', 1) | |
289 | if need_ofp_version: | |
290 | decl += ", enum ofp_version version" | |
291 | if base_argtype != 'void' and not base_argtype.startswith('struct'): | |
292 | decl += ", %s arg" % base_argtype | |
293 | decl += ")" | |
294 | if definitions: | |
295 | decl += "{\n" | |
296 | decl += " " | |
297 | if base_argtype.startswith('struct'): | |
298 | decl += "return " | |
299 | decl += "ofpact_put_raw(openflow, " | |
300 | if need_ofp_version: | |
301 | decl += "version" | |
302 | else: | |
303 | decl += "%s" % versions[0]["version"] | |
304 | decl += ", %s, " % versions[0]["enum"] | |
305 | if base_argtype.startswith('struct') or base_argtype == 'void': | |
306 | decl += "0" | |
307 | else: | |
308 | ntoh = types[base_argtype]['ntoh'] | |
309 | if ntoh: | |
310 | decl += "%s(arg)" % ntoh | |
311 | else: | |
312 | decl += "arg" | |
313 | decl += ");\n" | |
314 | decl += "}" | |
315 | else: | |
316 | decl += ";" | |
d34a1cc0 JW |
317 | print(decl) |
318 | print("") | |
c2d936a4 BP |
319 | |
320 | if definitions: | |
d34a1cc0 | 321 | print("""\ |
c2d936a4 BP |
322 | static enum ofperr |
323 | ofpact_decode(const struct ofp_action_header *a, enum ofp_raw_action_type raw, | |
04f48a68 | 324 | enum ofp_version version, uint64_t arg, |
5c7c16d8 YHW |
325 | const struct vl_mff_map *vl_mff_map, |
326 | uint64_t *tlv_bitmap, struct ofpbuf *out) | |
c2d936a4 BP |
327 | { |
328 | switch (raw) {\ | |
d34a1cc0 | 329 | """) |
c2d936a4 BP |
330 | for versions in enums.values(): |
331 | enum = versions[0]["enum"] | |
d34a1cc0 | 332 | print(" case %s:" % enum) |
c2d936a4 | 333 | base_argtype = versions[0]["base_argtype"] |
04f48a68 | 334 | arg_vl_mff_map = versions[0]["arg_vl_mff_map"] |
c2d936a4 | 335 | if base_argtype == 'void': |
d34a1cc0 | 336 | print(" return decode_%s(out);" % enum) |
c2d936a4 BP |
337 | else: |
338 | if base_argtype.startswith('struct'): | |
339 | arg = "ALIGNED_CAST(const %s *, a)" % base_argtype | |
340 | else: | |
341 | hton = types[base_argtype]['hton'] | |
342 | if hton: | |
343 | arg = "%s(arg)" % hton | |
344 | else: | |
345 | arg = "arg" | |
04f48a68 | 346 | if arg_vl_mff_map: |
d34a1cc0 | 347 | print(" return decode_%s(%s, version, vl_mff_map, tlv_bitmap, out);" % (enum, arg)) |
04f48a68 | 348 | else: |
d34a1cc0 JW |
349 | print(" return decode_%s(%s, version, out);" % (enum, arg)) |
350 | print("") | |
351 | print("""\ | |
c2d936a4 BP |
352 | default: |
353 | OVS_NOT_REACHED(); | |
354 | } | |
355 | }\ | |
d34a1cc0 | 356 | """) |
c2d936a4 BP |
357 | else: |
358 | for versions in enums.values(): | |
359 | enum = versions[0]["enum"] | |
360 | prototype = "static enum ofperr decode_%s(" % enum | |
361 | base_argtype = versions[0]["base_argtype"] | |
04f48a68 | 362 | arg_vl_mff_map = versions[0]["arg_vl_mff_map"] |
c2d936a4 BP |
363 | if base_argtype != 'void': |
364 | if base_argtype.startswith('struct'): | |
f3cd3ac7 | 365 | prototype += "const %s *, enum ofp_version, " % base_argtype |
c2d936a4 | 366 | else: |
f3cd3ac7 | 367 | prototype += "%s, enum ofp_version, " % base_argtype |
04f48a68 | 368 | if arg_vl_mff_map: |
5c7c16d8 | 369 | prototype += 'const struct vl_mff_map *, uint64_t *, ' |
c2d936a4 | 370 | prototype += "struct ofpbuf *);" |
d34a1cc0 | 371 | print(prototype) |
c2d936a4 | 372 | |
d34a1cc0 | 373 | print(""" |
c2d936a4 BP |
374 | static enum ofperr ofpact_decode(const struct ofp_action_header *, |
375 | enum ofp_raw_action_type raw, | |
f3cd3ac7 | 376 | enum ofp_version version, |
04f48a68 | 377 | uint64_t arg, const struct vl_mff_map *vl_mff_map, |
5c7c16d8 | 378 | uint64_t *tlv_bitmap, struct ofpbuf *out); |
d34a1cc0 | 379 | """) |
c2d936a4 BP |
380 | |
381 | if __name__ == '__main__': | |
382 | if '--help' in sys.argv: | |
383 | usage() | |
384 | elif len(sys.argv) != 3: | |
385 | sys.stderr.write("exactly two arguments required; " | |
386 | "use --help for help\n") | |
387 | sys.exit(1) | |
388 | elif sys.argv[2] == '--prototypes': | |
389 | extract_ofp_actions(sys.argv[1], False) | |
390 | elif sys.argv[2] == '--definitions': | |
391 | extract_ofp_actions(sys.argv[1], True) | |
392 | else: | |
393 | sys.stderr.write("invalid arguments; use --help for help\n") | |
394 | sys.exit(1) | |
395 |