ofp-msgs: Accept all versions of OpenFlow "hello" messages.
[openvswitch] / build-aux / extract-ofp-msgs
1 #! /usr/bin/python
2
3 import sys
4 import os.path
5 import re
6
7 line = ""
8
9 OFP10_VERSION = 0x01
10 OFP11_VERSION = 0x02
11 OFP12_VERSION = 0x03
12 OFP13_VERSION = 0x04
13
14 NX_VENDOR_ID = 0x00002320
15
16 OFPT_VENDOR = 4
17 OFPT10_STATS_REQUEST = 16
18 OFPT10_STATS_REPLY = 17
19 OFPT11_STATS_REQUEST = 18
20 OFPT11_STATS_REPLY = 19
21 OFPST_VENDOR = 0xffff
22
23 version_map = {"1.0":     (OFP10_VERSION, OFP10_VERSION),
24                "1.1":     (OFP11_VERSION, OFP11_VERSION),
25                "1.2":     (OFP12_VERSION, OFP12_VERSION),
26                "1.3":     (OFP13_VERSION, OFP13_VERSION),
27                "1.0+":    (OFP10_VERSION, OFP13_VERSION),
28                "1.1+":    (OFP11_VERSION, OFP13_VERSION),
29                "1.2+":    (OFP12_VERSION, OFP13_VERSION),
30                "1.3+":    (OFP13_VERSION, OFP13_VERSION),
31                "1.0-1.1": (OFP10_VERSION, OFP11_VERSION),
32                "1.0-1.2": (OFP10_VERSION, OFP12_VERSION),
33                "<all>":   (0x01, 0xff)}
34
35 def get_line():
36     global line
37     global line_number
38     line = input_file.readline()
39     line_number += 1
40     if line == "":
41         fatal("unexpected end of input")
42
43 n_errors = 0
44 def error(msg):
45     global n_errors
46     sys.stderr.write("%s:%d: %s\n" % (file_name, line_number, msg))
47     n_errors += 1
48
49 def fatal(msg):
50     error(msg)
51     sys.exit(1)
52
53 def usage():
54     argv0 = os.path.basename(sys.argv[0])
55     print '''\
56 %(argv0)s, for extracting OpenFlow message types from header files
57 usage: %(argv0)s INPUT OUTPUT
58   where INPUT is the name of the input header file
59     and OUTPUT is the output file name.
60 Despite OUTPUT, the output is written to stdout, and the OUTPUT argument
61 only controls #line directives in the output.\
62 ''' % {"argv0": argv0}
63     sys.exit(0)
64
65 def make_sizeof(s):
66     m = re.match(r'(.*) up to (.*)', s)
67     if m:
68         struct, member = m.groups()
69         return "offsetof(%s, %s)" % (struct, member)
70     else:
71         return "sizeof(%s)" % s
72
73 def extract_ofp_msgs(output_file_name):
74     raw_types = []
75
76     all_hdrs = {}
77     all_raws = {}
78     all_raws_order = []
79
80     while True:
81         get_line()
82         if re.match('enum ofpraw', line):
83             break
84
85     while True:
86         get_line()
87         first_line_number = line_number
88         here = '%s:%d' % (file_name, line_number)
89         if (line.startswith('/*')
90             or line.startswith(' *')
91             or not line
92             or line.isspace()):
93             continue
94         elif re.match('}', line):
95             break
96
97         if not line.lstrip().startswith('/*'):
98             fatal("unexpected syntax between ofpraw types")
99
100         comment = line.lstrip()[2:].strip()
101         while not comment.endswith('*/'):
102             get_line()
103             if line.startswith('/*') or not line or line.isspace():
104                 fatal("unexpected syntax within error")
105             comment += ' %s' % line.lstrip('* \t').rstrip(' \t\r\n')
106         comment = comment[:-2].rstrip()
107
108         m = re.match(r'([A-Z]+) ([-.+\d]+|<all>) \((\d+)\): ([^.]+)\.$', comment)
109         if not m:
110             fatal("unexpected syntax between messages")
111         type_, versions, number, contents = m.groups()
112         number = int(number)
113
114         get_line()
115         m = re.match('\s+(?:OFPRAW_%s)(\d*)_([A-Z0-9_]+),?$' % type_,
116                      line)
117         if not m:
118             fatal("syntax error expecting OFPRAW_ enum")
119         vinfix, name = m.groups()
120         rawname = 'OFPRAW_%s%s_%s' % (type_, vinfix, name)
121
122         min_version, max_version = version_map[versions]
123
124         human_name = '%s_%s' % (type_, name)
125         if type_.endswith('ST'):
126             if rawname.endswith('_REQUEST'):
127                 human_name = human_name[:-8] + " request"
128             elif rawname.endswith('_REPLY'):
129                 human_name = human_name[:-6] + " reply"
130             else:
131                 fatal("%s messages are statistics but %s doesn't end "
132                       "in _REQUEST or _REPLY" % (type_, rawname))
133
134         these_hdrs = []
135         for version in range(min_version, max_version + 1):
136             if type_ == 'OFPT':
137                 if number == OFPT_VENDOR:
138                     fatal("OFPT (%d) is used for vendor extensions"
139                           % number)
140                 elif (version == OFP10_VERSION
141                       and (number == OFPT10_STATS_REQUEST
142                            or number == OFPT10_STATS_REPLY)):
143                     fatal("OFPT 1.0 (%d) is used for stats messages"
144                           % number)
145                 elif (version != OFP10_VERSION
146                       and (number == OFPT11_STATS_REQUEST
147                            or number == OFPT11_STATS_REPLY)):
148                     fatal("OFPT 1.1+ (%d) is used for stats messages"
149                           % number)
150                 hdrs = (version, number, 0, 0, 0)
151             elif type_ == 'OFPST' and name.endswith('_REQUEST'):
152                 if version == OFP10_VERSION:
153                     hdrs = (version, OFPT10_STATS_REQUEST, number, 0, 0)
154                 else:
155                     hdrs = (version, OFPT11_STATS_REQUEST, number, 0, 0)
156             elif type_ == 'OFPST' and name.endswith('_REPLY'):
157                 if version == OFP10_VERSION:
158                     hdrs = (version, OFPT10_STATS_REPLY, number, 0, 0)
159                 else:
160                     hdrs = (version, OFPT11_STATS_REPLY, number, 0, 0)
161             elif type_ == 'NXT':
162                 hdrs = (version, OFPT_VENDOR, 0, NX_VENDOR_ID, number)
163             elif type_ == 'NXST' and name.endswith('_REQUEST'):
164                 if version == OFP10_VERSION:
165                     hdrs = (version, OFPT10_STATS_REQUEST, OFPST_VENDOR,
166                             NX_VENDOR_ID, number)
167                 else:
168                     hdrs = (version, OFPT11_STATS_REQUEST, OFPST_VENDOR,
169                             NX_VENDOR_ID, number)
170             elif type_ == 'NXST' and name.endswith('_REPLY'):
171                 if version == OFP10_VERSION:
172                     hdrs = (version, OFPT10_STATS_REPLY, OFPST_VENDOR,
173                             NX_VENDOR_ID, number)
174                 else:
175                     hdrs = (version, OFPT11_STATS_REPLY, OFPST_VENDOR,
176                             NX_VENDOR_ID, number)
177             else:
178                 fatal("type '%s' unknown" % type_)
179
180             if hdrs in all_hdrs:
181                 error("Duplicate message definition for %s." % str(hdrs))
182                 sys.stderr.write("%s: Here is the location "
183                                  "of the previous definition.\n"
184                                  % (all_hdrs[hdrs]))
185             all_hdrs[hdrs] = here
186             these_hdrs.append(hdrs)
187
188         extra_multiple = '0'
189         if contents == 'void':
190             min_body = '0'
191         else:
192             min_body_elem = []
193             for c in [s.strip() for s in contents.split(",")]:
194                 if c.endswith('[]'):
195                     if extra_multiple == '0':
196                         extra_multiple = make_sizeof(c[:-2])
197                     else:
198                         error("Cannot have multiple [] elements")
199                 else:
200                     min_body_elem.append(c)
201
202             if min_body_elem:
203                 min_body = " + ".join([make_sizeof(s)
204                                        for s in min_body_elem])
205             else:
206                 if extra_multiple == '0':
207                     error("Must specify contents (use 'void' if empty)")
208                 min_body = 0
209
210         if rawname in all_raws:
211             fatal("%s: Duplicate name" % rawname)
212
213         all_raws[rawname] = {"hdrs": these_hdrs,
214                              "min_version": min_version,
215                              "max_version": max_version,
216                              "min_body": min_body,
217                              "extra_multiple": extra_multiple,
218                              "type": type_,
219                              "human_name": human_name,
220                              "line": first_line_number}
221         all_raws_order.append(rawname)
222
223         continue
224
225     while True:
226         get_line()
227         if re.match('enum ofptype', line):
228             break
229
230     while True:
231         get_line()
232         if re.match(r'\s*/?\*', line) or line.isspace():
233             continue
234         elif re.match('}', line):
235             break
236
237         if not re.match(r'\s*OFPTYPE_.*/\*', line):
238             fatal("unexpected syntax between OFPTYPE_ definitions")
239
240         syntax = line.strip()
241         while not syntax.endswith('*/'):
242             get_line()
243             if not line.strip().startswith('*'):
244                 fatal("unexpected syntax within OFPTYPE_ definition")
245             syntax += ' %s' % line.strip().lstrip('* \t')
246             syntax = syntax.strip()
247
248         m = re.match(r'(OFPTYPE_[A-Z0-9_]+),\s*/\* (.*) \*/', syntax)
249         if not m:
250             fatal("syntax error in OFPTYPE_ definition")
251
252         ofptype, raws_ = m.groups()
253         raws = [s.rstrip('.') for s in raws_.split()]
254         for raw in raws:
255             if not re.match('OFPRAW_[A-Z0-9_]+$', raw):
256                 fatal("%s: invalid OFPRAW_* name syntax" % raw)
257             if raw not in all_raws:
258                 fatal("%s: not a declared OFPRAW_* name" % raw)
259             if "ofptype" in all_raws[raw]:
260                 fatal("%s: already part of %s"
261                       % (raw, all_raws[raw]["ofptype"]))
262             all_raws[raw]["ofptype"] = ofptype
263
264     input_file.close()
265
266     if n_errors:
267         sys.exit(1)
268
269     output = []
270     output.append("/* Generated automatically; do not modify!     "
271                   "-*- buffer-read-only: t -*- */")
272     output.append("")
273
274     for raw in all_raws_order:
275         r = all_raws[raw]
276         output.append("static struct raw_instance %s_instances[] = {"
277                       % raw.lower())
278         for hdrs in r['hdrs']:
279             output.append("    { {0, NULL}, {%d, %d, %d, 0x%x, %d}, %s, 0 },"
280                           % (hdrs + (raw,)))
281                 
282         output.append("};")
283
284     output.append("")
285
286     output.append("static struct raw_info raw_infos[] = {")
287     for raw in all_raws_order:
288         r = all_raws[raw]
289         if "ofptype" not in r:
290             error("%s: no defined OFPTYPE_" % raw)
291             continue
292         output.append("    {")
293         output.append("        %s_instances," % raw.lower())
294         output.append("        %d, %d," % (r["min_version"], r["max_version"]))
295         output.append("#line %s \"%s\"" % (r["line"], file_name))
296         output.append("        %s," % r["min_body"])
297         output.append("#line %s \"%s\"" % (r["line"], file_name))
298         output.append("        %s," % r["extra_multiple"])
299         output.append("#line %s \"%s\"" % (len(output) + 2, output_file_name))
300         output.append("        %s," % r["ofptype"])
301         output.append("        \"%s\"," % r["human_name"])
302         output.append("    },")
303
304         if r['type'].endswith("ST"):
305             for hdrs in r['hdrs']:
306                 op_hdrs = list(hdrs)
307                 if hdrs[0] == OFP10_VERSION:
308                     if hdrs[1] == OFPT10_STATS_REQUEST:
309                         op_hdrs[1] = OFPT10_STATS_REPLY
310                     elif hdrs[1] == OFPT10_STATS_REPLY:
311                         op_hdrs[1] = OFPT10_STATS_REQUEST
312                     else:
313                         assert False
314                 else:
315                     if hdrs[1] == OFPT11_STATS_REQUEST:
316                         op_hdrs[1] = OFPT11_STATS_REPLY
317                     elif hdrs[1] == OFPT11_STATS_REPLY:
318                         op_hdrs[1] = OFPT11_STATS_REQUEST
319                     else:
320                         assert False
321                 if tuple(op_hdrs) not in all_hdrs:
322                     if r["human_name"].endswith("request"):
323                         fatal("%s has no corresponding reply"
324                               % r["human_name"])
325                     else:
326                         fatal("%s has no corresponding request"
327                               % r["human_name"])
328     output.append("};")
329
330     if n_errors:
331         sys.exit(1)
332
333     return output
334
335
336 if __name__ == '__main__':
337     if '--help' in sys.argv:
338         usage()
339     elif len(sys.argv) != 3:
340         sys.stderr.write("exactly one non-option arguments required; "
341                          "use --help for help\n")
342         sys.exit(1)
343     else:
344         global file_name
345         global input_file
346         global line_number
347         file_name = sys.argv[1]
348         input_file = open(file_name)
349         line_number = 0
350
351         for line in extract_ofp_msgs(sys.argv[2]):
352             print line
353