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