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