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