nroff: Fix style of names.
[cascardo/ovs.git] / build-aux / extract-ofp-fields
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 TYPES = {"u8":       (1,   False),
18          "be16":     (2,   False),
19          "be32":     (4,   False),
20          "MAC":      (6,   False),
21          "be64":     (8,   False),
22          "IPv6":     (16,  False),
23          "tunnelMD": (124, True)}
24
25 FORMATTING = {"decimal":            ("MFS_DECIMAL",      1,   8),
26               "hexadecimal":        ("MFS_HEXADECIMAL",  1, 127),
27               "Ethernet":           ("MFS_ETHERNET",     6,   6),
28               "IPv4":               ("MFS_IPV4",         4,   4),
29               "IPv6":               ("MFS_IPV6",        16,  16),
30               "OpenFlow 1.0 port":  ("MFS_OFP_PORT",     2,   2),
31               "OpenFlow 1.1+ port": ("MFS_OFP_PORT_OXM", 4,   4),
32               "frag":               ("MFS_FRAG",         1,   1),
33               "tunnel flags":       ("MFS_TNL_FLAGS",    2,   2),
34               "TCP flags":          ("MFS_TCP_FLAGS",    2,   2)}
35
36 PREREQS = {"none": "MFP_NONE",
37            "ARP": "MFP_ARP",
38            "VLAN VID": "MFP_VLAN_VID",
39            "IPv4": "MFP_IPV4",
40            "IPv6": "MFP_IPV6",
41            "IPv4/IPv6": "MFP_IP_ANY",
42            "MPLS": "MFP_MPLS",
43            "TCP": "MFP_TCP",
44            "UDP": "MFP_UDP",
45            "SCTP": "MFP_SCTP",
46            "ICMPv4": "MFP_ICMPV4",
47            "ICMPv6": "MFP_ICMPV6",
48            "ND": "MFP_ND",
49            "ND solicit": "MFP_ND_SOLICIT",
50            "ND advert": "MFP_ND_ADVERT"}
51
52 # Maps a name prefix into an (experimenter ID, class) pair, so:
53 #
54 #      - Standard OXM classes are written as (0, <oxm_class>)
55 #
56 #      - Experimenter OXM classes are written as (<oxm_vender>, 0xffff)
57 #
58 # If a name matches more than one prefix, the longest one is used.
59 OXM_CLASSES = {"NXM_OF_":        (0,          0x0000),
60                "NXM_NX_":        (0,          0x0001),
61                "OXM_OF_":        (0,          0x8000),
62                "OXM_OF_PKT_REG": (0,          0x8001),
63                "ONFOXM_ET_":     (0x4f4e4600, 0xffff),
64
65                # This is the experimenter OXM class for Nicira, which is the
66                # one that OVS would be using instead of NXM_OF_ and NXM_NX_
67                # if OVS didn't have those grandfathered in.  It is currently
68                # used only to test support for experimenter OXM, since there
69                # are barely any real uses of experimenter OXM in the wild.
70                "NXOXM_ET_":      (0x00002320, 0xffff)}
71
72
73 def oxm_name_to_class(name):
74     prefix = ''
75     class_ = None
76     for p, c in OXM_CLASSES.items():
77         if name.startswith(p) and len(p) > len(prefix):
78             prefix = p
79             class_ = c
80     return class_
81
82
83 def decode_version_range(range):
84     if range in VERSION:
85         return (VERSION[range], VERSION[range])
86     elif range.endswith('+'):
87         return (VERSION[range[:-1]], max(VERSION.values()))
88     else:
89         a, b = re.match(r'^([^-]+)-([^-]+)$', range).groups()
90         return (VERSION[a], VERSION[b])
91
92
93 def get_line():
94     global line
95     global line_number
96     line = input_file.readline()
97     line_number += 1
98     if line == "":
99         fatal("unexpected end of input")
100
101
102 n_errors = 0
103
104
105 def error(msg):
106     global n_errors
107     sys.stderr.write("%s:%d: %s\n" % (file_name, line_number, msg))
108     n_errors += 1
109
110
111 def fatal(msg):
112     error(msg)
113     sys.exit(1)
114
115
116 def usage():
117     argv0 = os.path.basename(sys.argv[0])
118     print('''\
119 %(argv0)s, for extracting OpenFlow field properties from meta-flow.h
120 usage: %(argv0)s INPUT [--meta-flow | --nx-match]
121   where INPUT points to lib/meta-flow.h in the source directory.
122 Depending on the option given, the output written to stdout is intended to be
123 saved either as lib/meta-flow.inc or lib/nx-match.inc for the respective C
124 file to #include.\
125 ''' % {"argv0": argv0})
126     sys.exit(0)
127
128
129 def make_sizeof(s):
130     m = re.match(r'(.*) up to (.*)', s)
131     if m:
132         struct, member = m.groups()
133         return "offsetof(%s, %s)" % (struct, member)
134     else:
135         return "sizeof(%s)" % s
136
137
138 def parse_oxms(s, prefix, n_bytes):
139     if s == 'none':
140         return ()
141
142     return tuple(parse_oxm(s2.strip(), prefix, n_bytes) for s2 in s.split(','))
143
144
145 match_types = dict()
146
147
148 def parse_oxm(s, prefix, n_bytes):
149     global match_types
150
151     m = re.match('([A-Z0-9_]+)\(([0-9]+)\) since(?: OF(1\.[0-9]+) and)? v([12]\.[0-9]+)$', s)
152     if not m:
153         fatal("%s: syntax error parsing %s" % (s, prefix))
154
155     name, oxm_type, of_version, ovs_version = m.groups()
156
157     class_ = oxm_name_to_class(name)
158     if class_ is None:
159         fatal("unknown OXM class for %s" % name)
160     oxm_vendor, oxm_class = class_
161
162     if class_ in match_types:
163         if oxm_type in match_types[class_]:
164             fatal("duplicate match type for %s (conflicts with %s)" %
165                   (name, match_types[class_][oxm_type]))
166     else:
167         match_types[class_] = dict()
168     match_types[class_][oxm_type] = name
169
170     # Normally the oxm_length is the size of the field, but for experimenter
171     # OXMs oxm_length also includes the 4-byte experimenter ID.
172     oxm_length = n_bytes
173     if oxm_class == 0xffff:
174         oxm_length += 4
175
176     header = ("NXM_HEADER(0x%x,0x%x,%s,0,%d)"
177               % (oxm_vendor, oxm_class, oxm_type, oxm_length))
178
179     if of_version:
180         if of_version not in VERSION:
181             fatal("%s: unknown OpenFlow version %s" % (name, of_version))
182         of_version_nr = VERSION[of_version]
183         if of_version_nr < VERSION['1.2']:
184             fatal("%s: claimed version %s predates OXM" % (name, of_version))
185     else:
186         of_version_nr = 0
187
188     return (header, name, of_version_nr, ovs_version)
189
190
191 def parse_field(mff, comment):
192     f = {'mff': mff}
193
194     # First line of comment is the field name.
195     m = re.match(r'"([^"]+)"(?:\s+\(aka "([^"]+)"\))?(?:\s+\(.*\))?\.', comment[0])
196     if not m:
197         fatal("%s lacks field name" % mff)
198     f['name'], f['extra_name'] = m.groups()
199
200     # Find the last blank line the comment.  The field definitions
201     # start after that.
202     blank = None
203     for i in range(len(comment)):
204         if not comment[i]:
205             blank = i
206     if not blank:
207         fatal("%s: missing blank line in comment" % mff)
208
209     d = {}
210     for key in ("Type", "Maskable", "Formatting", "Prerequisites",
211                 "Access", "Prefix lookup member",
212                 "OXM", "NXM", "OF1.0", "OF1.1"):
213         d[key] = None
214     for fline in comment[blank + 1:]:
215         m = re.match(r'([^:]+):\s+(.*)\.$', fline)
216         if not m:
217             fatal("%s: syntax error parsing key-value pair as part of %s"
218                   % (fline, mff))
219         key, value = m.groups()
220         if key not in d:
221             fatal("%s: unknown key" % key)
222         elif key == 'Code point':
223             d[key] += [value]
224         elif d[key] is not None:
225             fatal("%s: duplicate key" % key)
226         d[key] = value
227     for key, value in d.items():
228         if not value and key not in ("OF1.0", "OF1.1",
229                                      "Prefix lookup member", "Notes"):
230             fatal("%s: missing %s" % (mff, key))
231
232     m = re.match(r'([a-zA-Z0-9]+)(?: \(low ([0-9]+) bits\))?$', d['Type'])
233     if not m:
234         fatal("%s: syntax error in type" % mff)
235     type_ = m.group(1)
236     if type_ not in TYPES:
237         fatal("%s: unknown type %s" % (mff, d['Type']))
238
239     f['n_bytes'] = TYPES[type_][0]
240     if m.group(2):
241         f['n_bits'] = int(m.group(2))
242         if f['n_bits'] > f['n_bytes'] * 8:
243             fatal("%s: more bits (%d) than field size (%d)"
244                   % (mff, f['n_bits'], 8 * f['n_bytes']))
245     else:
246         f['n_bits'] = 8 * f['n_bytes']
247     f['variable'] = TYPES[type_][1]
248
249     if d['Maskable'] == 'no':
250         f['mask'] = 'MFM_NONE'
251     elif d['Maskable'] == 'bitwise':
252         f['mask'] = 'MFM_FULLY'
253     else:
254         fatal("%s: unknown maskable %s" % (mff, d['Maskable']))
255
256     fmt = FORMATTING.get(d['Formatting'])
257     if not fmt:
258         fatal("%s: unknown format %s" % (mff, d['Formatting']))
259     if f['n_bytes'] < fmt[1] or f['n_bytes'] > fmt[2]:
260         fatal("%s: %d-byte field can't be formatted as %s"
261               % (mff, f['n_bytes'], d['Formatting']))
262     f['string'] = fmt[0]
263
264     f['prereqs'] = PREREQS.get(d['Prerequisites'])
265     if not f['prereqs']:
266         fatal("%s: unknown prerequisites %s" % (mff, d['Prerequisites']))
267
268     if d['Access'] == 'read-only':
269         f['writable'] = False
270     elif d['Access'] == 'read/write':
271         f['writable'] = True
272     else:
273         fatal("%s: unknown access %s" % (mff, d['Access']))
274
275     f['OF1.0'] = d['OF1.0']
276     if not d['OF1.0'] in (None, 'exact match', 'CIDR mask'):
277         fatal("%s: unknown OF1.0 match type %s" % (mff, d['OF1.0']))
278
279     f['OF1.1'] = d['OF1.1']
280     if not d['OF1.1'] in (None, 'exact match', 'bitwise mask'):
281         fatal("%s: unknown OF1.1 match type %s" % (mff, d['OF1.1']))
282
283     f['OXM'] = (parse_oxms(d['OXM'], 'OXM', f['n_bytes']) +
284                 parse_oxms(d['NXM'], 'NXM', f['n_bytes']))
285
286     f['prefix'] = d["Prefix lookup member"]
287
288     return f
289
290
291 def protocols_to_c(protocols):
292     if protocols == set(['of10', 'of11', 'oxm']):
293         return 'OFPUTIL_P_ANY'
294     elif protocols == set(['of11', 'oxm']):
295         return 'OFPUTIL_P_NXM_OF11_UP'
296     elif protocols == set(['oxm']):
297         return 'OFPUTIL_P_NXM_OXM_ANY'
298     elif protocols == set([]):
299         return 'OFPUTIL_P_NONE'
300     else:
301         assert False
302
303
304 def make_meta_flow(fields):
305     output = []
306     for f in fields:
307         output += ["{"]
308         output += ["    %s," % f['mff']]
309         if f['extra_name']:
310             output += ["    \"%s\", \"%s\"," % (f['name'], f['extra_name'])]
311         else:
312             output += ["    \"%s\", NULL," % f['name']]
313
314         if f['variable']:
315             variable = 'true'
316         else:
317             variable = 'false'
318         output += ["    %d, %d, %s," % (f['n_bytes'], f['n_bits'], variable)]
319
320         if f['writable']:
321             rw = 'true'
322         else:
323             rw = 'false'
324         output += ["    %s, %s, %s, %s,"
325                    % (f['mask'], f['string'], f['prereqs'], rw)]
326
327         oxm = f['OXM']
328         of10 = f['OF1.0']
329         of11 = f['OF1.1']
330         if f['mff'] in ('MFF_DL_VLAN', 'MFF_DL_VLAN_PCP'):
331             # MFF_DL_VLAN and MFF_DL_VLAN_PCP don't exactly correspond to
332             # OF1.1, nor do they have NXM or OXM assignments, but their
333             # meanings can be expressed in every protocol, which is the goal of
334             # this member.
335             protocols = set(["of10", "of11", "oxm"])
336         else:
337             protocols = set([])
338             if of10:
339                 protocols |= set(["of10"])
340             if of11:
341                 protocols |= set(["of11"])
342             if oxm:
343                 protocols |= set(["oxm"])
344
345         if f['mask'] == 'MFM_FULLY':
346             cidr_protocols = protocols.copy()
347             bitwise_protocols = protocols.copy()
348
349             if of10 == 'exact match':
350                 bitwise_protocols -= set(['of10'])
351                 cidr_protocols -= set(['of10'])
352             elif of10 == 'CIDR mask':
353                 bitwise_protocols -= set(['of10'])
354             else:
355                 assert of10 is None
356
357             if of11 == 'exact match':
358                 bitwise_protocols -= set(['of11'])
359                 cidr_protocols -= set(['of11'])
360             else:
361                 assert of11 in (None, 'bitwise mask')
362         else:
363             assert f['mask'] == 'MFM_NONE'
364             cidr_protocols = set([])
365             bitwise_protocols = set([])
366
367         output += ["    %s," % protocols_to_c(protocols)]
368         output += ["    %s," % protocols_to_c(cidr_protocols)]
369         output += ["    %s," % protocols_to_c(bitwise_protocols)]
370
371         if f['prefix']:
372             output += ["    FLOW_U32OFS(%s)," % f['prefix']]
373         else:
374             output += ["    -1, /* not usable for prefix lookup */"]
375
376         output += ["},"]
377     return output
378
379
380 def make_nx_match(fields):
381     output = []
382     print("static struct nxm_field_index all_nxm_fields[] = {")
383     for f in fields:
384         # Sort by OpenFlow version number (nx-match.c depends on this).
385         for oxm in sorted(f['OXM'], key=lambda x: x[2]):
386             print("""{ .nf = { %s, %d, "%s", %s } },""" % (
387                 oxm[0], oxm[2], oxm[1], f['mff']))
388     print("};")
389     return output
390
391
392 def extract_ofp_fields(mode):
393     global line
394
395     fields = []
396
397     while True:
398         get_line()
399         if re.match('enum.*mf_field_id', line):
400             break
401
402     while True:
403         get_line()
404         first_line_number = line_number
405         here = '%s:%d' % (file_name, line_number)
406         if (line.startswith('/*')
407             or line.startswith(' *')
408             or line.startswith('#')
409             or not line
410             or line.isspace()):
411             continue
412         elif re.match('}', line) or re.match('\s+MFF_N_IDS', line):
413             break
414
415         # Parse the comment preceding an MFF_ constant into 'comment',
416         # one line to an array element.
417         line = line.strip()
418         if not line.startswith('/*'):
419             fatal("unexpected syntax between fields")
420         line = line[1:]
421         comment = []
422         end = False
423         while not end:
424             line = line.strip()
425             if line.startswith('*/'):
426                 get_line()
427                 break
428             if not line.startswith('*'):
429                 fatal("unexpected syntax within field")
430
431             line = line[1:]
432             if line.startswith(' '):
433                 line = line[1:]
434             if line.startswith(' ') and comment:
435                 continuation = True
436                 line = line.lstrip()
437             else:
438                 continuation = False
439
440             if line.endswith('*/'):
441                 line = line[:-2].rstrip()
442                 end = True
443             else:
444                 end = False
445
446             if continuation:
447                 comment[-1] += " " + line
448             else:
449                 comment += [line]
450             get_line()
451
452         # Drop blank lines at each end of comment.
453         while comment and not comment[0]:
454             comment = comment[1:]
455         while comment and not comment[-1]:
456             comment = comment[:-1]
457
458         # Parse the MFF_ constant(s).
459         mffs = []
460         while True:
461             m = re.match('\s+(MFF_[A-Z0-9_]+),?\s?$', line)
462             if not m:
463                 break
464             mffs += [m.group(1)]
465             get_line()
466         if not mffs:
467             fatal("unexpected syntax looking for MFF_ constants")
468
469         if len(mffs) > 1 or '<N>' in comment[0]:
470             for mff in mffs:
471                 # Extract trailing integer.
472                 m = re.match('.*[^0-9]([0-9]+)$', mff)
473                 if not m:
474                     fatal("%s lacks numeric suffix in register group" % mff)
475                 n = m.group(1)
476
477                 # Search-and-replace <N> within the comment,
478                 # and drop lines that have <x> for x != n.
479                 instance = []
480                 for x in comment:
481                     y = x.replace('<N>', n)
482                     if re.search('<[0-9]+>', y):
483                         if ('<%s>' % n) not in y:
484                             continue
485                         y = re.sub('<[0-9]+>', '', y)
486                     instance += [y.strip()]
487                 fields += [parse_field(mff, instance)]
488         else:
489             fields += [parse_field(mffs[0], comment)]
490         continue
491
492     input_file.close()
493
494     if n_errors:
495         sys.exit(1)
496
497     print("""\
498 /* Generated automatically; do not modify!    "-*- buffer-read-only: t -*- */
499 """)
500
501     if mode == '--meta-flow':
502         output = make_meta_flow(fields)
503     elif mode == '--nx-match':
504         output = make_nx_match(fields)
505     else:
506         assert False
507
508     return output
509
510
511 if __name__ == '__main__':
512     if '--help' in sys.argv:
513         usage()
514     elif len(sys.argv) != 3:
515         sys.stderr.write("exactly two arguments required; "
516                          "use --help for help\n")
517         sys.exit(1)
518     elif sys.argv[2] in ('--meta-flow', '--nx-match'):
519         global file_name
520         global input_file
521         global line_number
522         file_name = sys.argv[1]
523         input_file = open(file_name)
524         line_number = 0
525
526         for oline in extract_ofp_fields(sys.argv[2]):
527             print(oline)
528     else:
529         sys.stderr.write("invalid arguments; use --help for help\n")
530         sys.exit(1)