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