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