]> git.proxmox.com Git - mirror_ovs.git/blame - build-aux/extract-ofp-fields
extract-ofp-fields: Improve error message.
[mirror_ovs.git] / build-aux / extract-ofp-fields
CommitLineData
a4ce8b25
BP
1#! /usr/bin/python
2
96fee5e0 3import getopt
a4ce8b25
BP
4import sys
5import os.path
6import re
96fee5e0
BP
7import xml.dom.minidom
8import build.nroff
a4ce8b25
BP
9
10line = ""
11
12# Maps from user-friendly version number to its protocol encoding.
13VERSION = {"1.0": 0x01,
14 "1.1": 0x02,
15 "1.2": 0x03,
16 "1.3": 0x04,
17 "1.4": 0x05,
18 "1.5": 0x06}
4ab66562 19VERSION_REVERSE = dict((v,k) for k, v in VERSION.items())
a4ce8b25 20
9558d2a5
JG
21TYPES = {"u8": (1, False),
22 "be16": (2, False),
23 "be32": (4, False),
24 "MAC": (6, False),
25 "be64": (8, False),
e56e30ac 26 "be128": (16, False),
9558d2a5 27 "tunnelMD": (124, True)}
a4ce8b25 28
1734bf29
JG
29FORMATTING = {"decimal": ("MFS_DECIMAL", 1, 8),
30 "hexadecimal": ("MFS_HEXADECIMAL", 1, 127),
07659514 31 "ct state": ("MFS_CT_STATE", 4, 4),
1734bf29
JG
32 "Ethernet": ("MFS_ETHERNET", 6, 6),
33 "IPv4": ("MFS_IPV4", 4, 4),
34 "IPv6": ("MFS_IPV6", 16, 16),
35 "OpenFlow 1.0 port": ("MFS_OFP_PORT", 2, 2),
36 "OpenFlow 1.1+ port": ("MFS_OFP_PORT_OXM", 4, 4),
37 "frag": ("MFS_FRAG", 1, 1),
38 "tunnel flags": ("MFS_TNL_FLAGS", 2, 2),
3d4b2e6e
JS
39 "TCP flags": ("MFS_TCP_FLAGS", 2, 2),
40 "packet type": ("MFS_PACKET_TYPE", 4, 4)}
a4ce8b25
BP
41
42PREREQS = {"none": "MFP_NONE",
beb75a40 43 "Ethernet": "MFP_ETHERNET",
56134136
JS
44 "ARP": "MFP_ARP",
45 "VLAN VID": "MFP_VLAN_VID",
46 "IPv4": "MFP_IPV4",
47 "IPv6": "MFP_IPV6",
48 "IPv4/IPv6": "MFP_IP_ANY",
3d2fbd70 49 "NSH": "MFP_NSH",
daf4d3c1 50 "CT": "MFP_CT_VALID",
56134136
JS
51 "MPLS": "MFP_MPLS",
52 "TCP": "MFP_TCP",
53 "UDP": "MFP_UDP",
54 "SCTP": "MFP_SCTP",
55 "ICMPv4": "MFP_ICMPV4",
56 "ICMPv6": "MFP_ICMPV6",
57 "ND": "MFP_ND",
58 "ND solicit": "MFP_ND_SOLICIT",
59 "ND advert": "MFP_ND_ADVERT"}
a4ce8b25 60
508a9338
BP
61# Maps a name prefix into an (experimenter ID, class) pair, so:
62#
63# - Standard OXM classes are written as (0, <oxm_class>)
64#
65# - Experimenter OXM classes are written as (<oxm_vender>, 0xffff)
66#
a4ce8b25 67# If a name matches more than one prefix, the longest one is used.
ffc317b9
BP
68OXM_CLASSES = {"NXM_OF_": (0, 0x0000, 'extension'),
69 "NXM_NX_": (0, 0x0001, 'extension'),
70 "NXOXM_NSH_": (0x005ad650, 0xffff, 'extension'),
71 "OXM_OF_": (0, 0x8000, 'standard'),
72 "OXM_OF_PKT_REG": (0, 0x8001, 'standard'),
73 "ONFOXM_ET_": (0x4f4e4600, 0xffff, 'standard'),
9b2b8497 74 "ERICOXM_OF_": (0, 0x1000, 'extension'),
508a9338
BP
75
76 # This is the experimenter OXM class for Nicira, which is the
77 # one that OVS would be using instead of NXM_OF_ and NXM_NX_
78 # if OVS didn't have those grandfathered in. It is currently
79 # used only to test support for experimenter OXM, since there
80 # are barely any real uses of experimenter OXM in the wild.
ffc317b9 81 "NXOXM_ET_": (0x00002320, 0xffff, 'extension')}
56134136 82
a4ce8b25
BP
83def oxm_name_to_class(name):
84 prefix = ''
85 class_ = None
1421f682 86 for p, c in OXM_CLASSES.items():
a4ce8b25
BP
87 if name.startswith(p) and len(p) > len(prefix):
88 prefix = p
89 class_ = c
90 return class_
91
56134136 92
ffc317b9
BP
93def is_standard_oxm(name):
94 oxm_vendor, oxm_class, oxm_class_type = oxm_name_to_class(name)
95 return oxm_class_type == 'standard'
96
97
a4ce8b25
BP
98def decode_version_range(range):
99 if range in VERSION:
100 return (VERSION[range], VERSION[range])
101 elif range.endswith('+'):
102 return (VERSION[range[:-1]], max(VERSION.values()))
103 else:
104 a, b = re.match(r'^([^-]+)-([^-]+)$', range).groups()
105 return (VERSION[a], VERSION[b])
106
56134136 107
a4ce8b25
BP
108def get_line():
109 global line
110 global line_number
111 line = input_file.readline()
112 line_number += 1
113 if line == "":
114 fatal("unexpected end of input")
115
56134136 116
a4ce8b25 117n_errors = 0
56134136
JS
118
119
a4ce8b25
BP
120def error(msg):
121 global n_errors
122 sys.stderr.write("%s:%d: %s\n" % (file_name, line_number, msg))
123 n_errors += 1
124
56134136 125
a4ce8b25
BP
126def fatal(msg):
127 error(msg)
128 sys.exit(1)
129
56134136 130
a4ce8b25
BP
131def usage():
132 argv0 = os.path.basename(sys.argv[0])
1421f682 133 print('''\
a4ce8b25 134%(argv0)s, for extracting OpenFlow field properties from meta-flow.h
178742f9 135usage: %(argv0)s INPUT [--meta-flow | --nx-match]
a4ce8b25 136 where INPUT points to lib/meta-flow.h in the source directory.
178742f9
BP
137Depending on the option given, the output written to stdout is intended to be
138saved either as lib/meta-flow.inc or lib/nx-match.inc for the respective C
139file to #include.\
1421f682 140''' % {"argv0": argv0})
a4ce8b25
BP
141 sys.exit(0)
142
56134136 143
a4ce8b25
BP
144def make_sizeof(s):
145 m = re.match(r'(.*) up to (.*)', s)
146 if m:
147 struct, member = m.groups()
148 return "offsetof(%s, %s)" % (struct, member)
149 else:
150 return "sizeof(%s)" % s
151
56134136 152
e6556fe3 153def parse_oxms(s, prefix, n_bytes):
a4ce8b25 154 if s == 'none':
e6556fe3
BP
155 return ()
156
157 return tuple(parse_oxm(s2.strip(), prefix, n_bytes) for s2 in s.split(','))
a4ce8b25 158
56134136 159
8c101866
JS
160match_types = dict()
161
162
e6556fe3 163def parse_oxm(s, prefix, n_bytes):
8c101866
JS
164 global match_types
165
a4ce8b25
BP
166 m = re.match('([A-Z0-9_]+)\(([0-9]+)\) since(?: OF(1\.[0-9]+) and)? v([12]\.[0-9]+)$', s)
167 if not m:
168 fatal("%s: syntax error parsing %s" % (s, prefix))
56134136 169
508a9338 170 name, oxm_type, of_version, ovs_version = m.groups()
a4ce8b25
BP
171
172 class_ = oxm_name_to_class(name)
173 if class_ is None:
174 fatal("unknown OXM class for %s" % name)
ffc317b9 175 oxm_vendor, oxm_class, oxm_class_type = class_
508a9338 176
8c101866
JS
177 if class_ in match_types:
178 if oxm_type in match_types[class_]:
179 fatal("duplicate match type for %s (conflicts with %s)" %
180 (name, match_types[class_][oxm_type]))
181 else:
182 match_types[class_] = dict()
183 match_types[class_][oxm_type] = name
184
508a9338
BP
185 # Normally the oxm_length is the size of the field, but for experimenter
186 # OXMs oxm_length also includes the 4-byte experimenter ID.
187 oxm_length = n_bytes
188 if oxm_class == 0xffff:
189 oxm_length += 4
190
96fee5e0 191 header = (oxm_vendor, oxm_class, int(oxm_type), oxm_length)
a4ce8b25
BP
192
193 if of_version:
ffc317b9
BP
194 if oxm_class_type == 'extension':
195 fatal("%s: OXM extension can't have OpenFlow version" % name)
a4ce8b25
BP
196 if of_version not in VERSION:
197 fatal("%s: unknown OpenFlow version %s" % (name, of_version))
198 of_version_nr = VERSION[of_version]
199 if of_version_nr < VERSION['1.2']:
200 fatal("%s: claimed version %s predates OXM" % (name, of_version))
201 else:
ffc317b9
BP
202 if oxm_class_type == 'standard':
203 fatal("%s: missing OpenFlow version number" % name)
a4ce8b25
BP
204 of_version_nr = 0
205
206 return (header, name, of_version_nr, ovs_version)
207
56134136 208
a4ce8b25
BP
209def parse_field(mff, comment):
210 f = {'mff': mff}
211
212 # First line of comment is the field name.
213 m = re.match(r'"([^"]+)"(?:\s+\(aka "([^"]+)"\))?(?:\s+\(.*\))?\.', comment[0])
214 if not m:
215 fatal("%s lacks field name" % mff)
216 f['name'], f['extra_name'] = m.groups()
217
218 # Find the last blank line the comment. The field definitions
219 # start after that.
220 blank = None
221 for i in range(len(comment)):
222 if not comment[i]:
223 blank = i
224 if not blank:
225 fatal("%s: missing blank line in comment" % mff)
226
227 d = {}
228 for key in ("Type", "Maskable", "Formatting", "Prerequisites",
229 "Access", "Prefix lookup member",
230 "OXM", "NXM", "OF1.0", "OF1.1"):
231 d[key] = None
232 for fline in comment[blank + 1:]:
233 m = re.match(r'([^:]+):\s+(.*)\.$', fline)
234 if not m:
235 fatal("%s: syntax error parsing key-value pair as part of %s"
236 % (fline, mff))
237 key, value = m.groups()
238 if key not in d:
239 fatal("%s: unknown key" % key)
240 elif key == 'Code point':
241 d[key] += [value]
242 elif d[key] is not None:
243 fatal("%s: duplicate key" % key)
244 d[key] = value
1421f682 245 for key, value in d.items():
a4ce8b25
BP
246 if not value and key not in ("OF1.0", "OF1.1",
247 "Prefix lookup member", "Notes"):
248 fatal("%s: missing %s" % (mff, key))
249
250 m = re.match(r'([a-zA-Z0-9]+)(?: \(low ([0-9]+) bits\))?$', d['Type'])
251 if not m:
252 fatal("%s: syntax error in type" % mff)
253 type_ = m.group(1)
254 if type_ not in TYPES:
255 fatal("%s: unknown type %s" % (mff, d['Type']))
f047e846
JG
256
257 f['n_bytes'] = TYPES[type_][0]
a4ce8b25
BP
258 if m.group(2):
259 f['n_bits'] = int(m.group(2))
260 if f['n_bits'] > f['n_bytes'] * 8:
261 fatal("%s: more bits (%d) than field size (%d)"
262 % (mff, f['n_bits'], 8 * f['n_bytes']))
263 else:
264 f['n_bits'] = 8 * f['n_bytes']
f047e846 265 f['variable'] = TYPES[type_][1]
a4ce8b25
BP
266
267 if d['Maskable'] == 'no':
268 f['mask'] = 'MFM_NONE'
269 elif d['Maskable'] == 'bitwise':
270 f['mask'] = 'MFM_FULLY'
271 else:
272 fatal("%s: unknown maskable %s" % (mff, d['Maskable']))
273
274 fmt = FORMATTING.get(d['Formatting'])
275 if not fmt:
276 fatal("%s: unknown format %s" % (mff, d['Formatting']))
96fee5e0 277 f['formatting'] = d['Formatting']
a4ce8b25
BP
278 if f['n_bytes'] < fmt[1] or f['n_bytes'] > fmt[2]:
279 fatal("%s: %d-byte field can't be formatted as %s"
280 % (mff, f['n_bytes'], d['Formatting']))
281 f['string'] = fmt[0]
282
96fee5e0
BP
283 f['prereqs'] = d['Prerequisites']
284 if f['prereqs'] not in PREREQS:
a4ce8b25
BP
285 fatal("%s: unknown prerequisites %s" % (mff, d['Prerequisites']))
286
287 if d['Access'] == 'read-only':
288 f['writable'] = False
289 elif d['Access'] == 'read/write':
290 f['writable'] = True
291 else:
292 fatal("%s: unknown access %s" % (mff, d['Access']))
293
294 f['OF1.0'] = d['OF1.0']
295 if not d['OF1.0'] in (None, 'exact match', 'CIDR mask'):
296 fatal("%s: unknown OF1.0 match type %s" % (mff, d['OF1.0']))
56134136 297
a4ce8b25
BP
298 f['OF1.1'] = d['OF1.1']
299 if not d['OF1.1'] in (None, 'exact match', 'bitwise mask'):
300 fatal("%s: unknown OF1.1 match type %s" % (mff, d['OF1.1']))
301
e6556fe3
BP
302 f['OXM'] = (parse_oxms(d['OXM'], 'OXM', f['n_bytes']) +
303 parse_oxms(d['NXM'], 'NXM', f['n_bytes']))
a4ce8b25
BP
304
305 f['prefix'] = d["Prefix lookup member"]
306
307 return f
308
56134136 309
a4ce8b25
BP
310def protocols_to_c(protocols):
311 if protocols == set(['of10', 'of11', 'oxm']):
312 return 'OFPUTIL_P_ANY'
313 elif protocols == set(['of11', 'oxm']):
314 return 'OFPUTIL_P_NXM_OF11_UP'
315 elif protocols == set(['oxm']):
316 return 'OFPUTIL_P_NXM_OXM_ANY'
317 elif protocols == set([]):
318 return 'OFPUTIL_P_NONE'
319 else:
56134136
JS
320 assert False
321
a4ce8b25 322
96fee5e0
BP
323def autogen_c_comment():
324 return [
325"/* Generated automatically; do not modify! -*- buffer-read-only: t -*- */",
326""]
327
328def make_meta_flow(meta_flow_h):
329 fields = extract_ofp_fields(meta_flow_h)
330 output = autogen_c_comment()
178742f9
BP
331 for f in fields:
332 output += ["{"]
333 output += [" %s," % f['mff']]
334 if f['extra_name']:
335 output += [" \"%s\", \"%s\"," % (f['name'], f['extra_name'])]
336 else:
337 output += [" \"%s\", NULL," % f['name']]
f047e846
JG
338
339 if f['variable']:
340 variable = 'true'
341 else:
342 variable = 'false'
343 output += [" %d, %d, %s," % (f['n_bytes'], f['n_bits'], variable)]
178742f9
BP
344
345 if f['writable']:
346 rw = 'true'
347 else:
348 rw = 'false'
04f48a68 349 output += [" %s, %s, %s, %s, false,"
96fee5e0 350 % (f['mask'], f['string'], PREREQS[f['prereqs']], rw)]
178742f9 351
178742f9 352 oxm = f['OXM']
178742f9
BP
353 of10 = f['OF1.0']
354 of11 = f['OF1.1']
355 if f['mff'] in ('MFF_DL_VLAN', 'MFF_DL_VLAN_PCP'):
356 # MFF_DL_VLAN and MFF_DL_VLAN_PCP don't exactly correspond to
357 # OF1.1, nor do they have NXM or OXM assignments, but their
358 # meanings can be expressed in every protocol, which is the goal of
359 # this member.
360 protocols = set(["of10", "of11", "oxm"])
361 else:
362 protocols = set([])
363 if of10:
364 protocols |= set(["of10"])
365 if of11:
366 protocols |= set(["of11"])
e6556fe3 367 if oxm:
178742f9
BP
368 protocols |= set(["oxm"])
369
370 if f['mask'] == 'MFM_FULLY':
371 cidr_protocols = protocols.copy()
372 bitwise_protocols = protocols.copy()
373
374 if of10 == 'exact match':
375 bitwise_protocols -= set(['of10'])
376 cidr_protocols -= set(['of10'])
377 elif of10 == 'CIDR mask':
378 bitwise_protocols -= set(['of10'])
379 else:
380 assert of10 is None
381
382 if of11 == 'exact match':
383 bitwise_protocols -= set(['of11'])
384 cidr_protocols -= set(['of11'])
385 else:
386 assert of11 in (None, 'bitwise mask')
387 else:
388 assert f['mask'] == 'MFM_NONE'
389 cidr_protocols = set([])
390 bitwise_protocols = set([])
391
392 output += [" %s," % protocols_to_c(protocols)]
393 output += [" %s," % protocols_to_c(cidr_protocols)]
394 output += [" %s," % protocols_to_c(bitwise_protocols)]
56134136 395
178742f9
BP
396 if f['prefix']:
397 output += [" FLOW_U32OFS(%s)," % f['prefix']]
398 else:
399 output += [" -1, /* not usable for prefix lookup */"]
400
aafee638 401 output += ["},"]
96fee5e0
BP
402 for oline in output:
403 print(oline)
178742f9 404
56134136 405
96fee5e0
BP
406def make_nx_match(meta_flow_h):
407 fields = extract_ofp_fields(meta_flow_h)
408 output = autogen_c_comment()
1421f682 409 print("static struct nxm_field_index all_nxm_fields[] = {")
178742f9 410 for f in fields:
e6556fe3
BP
411 # Sort by OpenFlow version number (nx-match.c depends on this).
412 for oxm in sorted(f['OXM'], key=lambda x: x[2]):
96fee5e0 413 header = ("NXM_HEADER(0x%x,0x%x,%s,0,%d)" % oxm[0])
1421f682 414 print("""{ .nf = { %s, %d, "%s", %s } },""" % (
96fee5e0 415 header, oxm[2], oxm[1], f['mff']))
1421f682 416 print("};")
96fee5e0
BP
417 for oline in output:
418 print(oline)
178742f9 419
56134136 420
96fee5e0
BP
421def extract_ofp_fields(fn):
422 global file_name
423 global input_file
424 global line_number
a4ce8b25
BP
425 global line
426
96fee5e0
BP
427 file_name = fn
428 input_file = open(file_name)
429 line_number = 0
430
a4ce8b25
BP
431 fields = []
432
433 while True:
434 get_line()
435 if re.match('enum.*mf_field_id', line):
436 break
437
438 while True:
439 get_line()
440 first_line_number = line_number
441 here = '%s:%d' % (file_name, line_number)
442 if (line.startswith('/*')
443 or line.startswith(' *')
444 or line.startswith('#')
445 or not line
446 or line.isspace()):
447 continue
448 elif re.match('}', line) or re.match('\s+MFF_N_IDS', line):
449 break
450
451 # Parse the comment preceding an MFF_ constant into 'comment',
452 # one line to an array element.
453 line = line.strip()
454 if not line.startswith('/*'):
455 fatal("unexpected syntax between fields")
456 line = line[1:]
457 comment = []
458 end = False
459 while not end:
460 line = line.strip()
461 if line.startswith('*/'):
462 get_line()
463 break
464 if not line.startswith('*'):
465 fatal("unexpected syntax within field")
466
467 line = line[1:]
468 if line.startswith(' '):
469 line = line[1:]
470 if line.startswith(' ') and comment:
471 continuation = True
472 line = line.lstrip()
473 else:
474 continuation = False
475
476 if line.endswith('*/'):
477 line = line[:-2].rstrip()
478 end = True
479 else:
480 end = False
481
482 if continuation:
483 comment[-1] += " " + line
484 else:
485 comment += [line]
486 get_line()
487
488 # Drop blank lines at each end of comment.
489 while comment and not comment[0]:
490 comment = comment[1:]
491 while comment and not comment[-1]:
492 comment = comment[:-1]
493
494 # Parse the MFF_ constant(s).
495 mffs = []
496 while True:
497 m = re.match('\s+(MFF_[A-Z0-9_]+),?\s?$', line)
498 if not m:
499 break
500 mffs += [m.group(1)]
501 get_line()
502 if not mffs:
503 fatal("unexpected syntax looking for MFF_ constants")
504
505 if len(mffs) > 1 or '<N>' in comment[0]:
506 for mff in mffs:
507 # Extract trailing integer.
508 m = re.match('.*[^0-9]([0-9]+)$', mff)
509 if not m:
510 fatal("%s lacks numeric suffix in register group" % mff)
511 n = m.group(1)
512
513 # Search-and-replace <N> within the comment,
514 # and drop lines that have <x> for x != n.
515 instance = []
516 for x in comment:
517 y = x.replace('<N>', n)
518 if re.search('<[0-9]+>', y):
519 if ('<%s>' % n) not in y:
520 continue
521 y = re.sub('<[0-9]+>', '', y)
522 instance += [y.strip()]
523 fields += [parse_field(mff, instance)]
524 else:
525 fields += [parse_field(mffs[0], comment)]
526 continue
527
528 input_file.close()
529
530 if n_errors:
531 sys.exit(1)
532
96fee5e0
BP
533 return fields
534\f
535## ------------------------ ##
536## Documentation Generation ##
537## ------------------------ ##
538
539def field_to_xml(field_node, f, body, summary):
540 f["used"] = True
541
542 # Summary.
543 if field_node.hasAttribute('internal'):
544 return
545
546 min_of_version = None
547 min_ovs_version = None
548 for header, name, of_version_nr, ovs_version_s in f['OXM']:
ffc317b9 549 if (is_standard_oxm(name)
96fee5e0
BP
550 and (min_ovs_version is None or of_version_nr < min_of_version)):
551 min_of_version = of_version_nr
552 ovs_version = [int(x) for x in ovs_version_s.split('.')]
553 if min_ovs_version is None or ovs_version < min_ovs_version:
554 min_ovs_version = ovs_version
555 summary += ["\\fB%s\\fR" % f["name"]]
556 if f["extra_name"]:
557 summary += [" aka \\fB%s\\fR" % f["extra_name"]]
558 summary += [";%d" % f["n_bytes"]]
559 if f["n_bits"] != 8 * f["n_bytes"]:
560 summary += [" (low %d bits)" % f["n_bits"]]
561 summary += [";%s;" % {"MFM_NONE": "no", "MFM_FULLY": "yes"}[f["mask"]]]
562 summary += ["%s;" % {True: "yes", False: "no"}[f["writable"]]]
563 summary += ["%s;" % f["prereqs"]]
564 support = []
565 if min_of_version is not None:
566 support += ["OF %s+" % VERSION_REVERSE[min_of_version]]
567 if min_ovs_version is not None:
568 support += ["OVS %s+" % '.'.join([str(x) for x in min_ovs_version])]
569 summary += ' and '.join(support)
570 summary += ["\n"]
571
572 # Full description.
573 if field_node.hasAttribute('hidden'):
574 return
575
576 title = field_node.attributes['title'].nodeValue
577
578 body += [""".PP
579\\fB%s Field\\fR
580.TS
581tab(;);
582l lx.
583""" % title]
584
585 body += ["Name:;\\fB%s\\fR" % f["name"]]
586 if f["extra_name"]:
587 body += [" (aka \\fB%s\\fR)" % f["extra_name"]]
588 body += ['\n']
589
590 body += ["Width:;"]
591 if f["n_bits"] != 8 * f["n_bytes"]:
592 body += ["%d bits (only the least-significant %d bits "
593 "may be nonzero)" % (f["n_bytes"] * 8, f["n_bits"])]
594 elif f["n_bits"] <= 128:
595 body += ["%d bits" % f["n_bits"]]
178742f9 596 else:
96fee5e0
BP
597 body += ["%d bits (%d bytes)" % (f["n_bits"], f["n_bits"] / 8)]
598 body += ['\n']
599
600 body += ["Format:;%s\n" % f["formatting"]]
601
602 masks = {"MFM_NONE": "not maskable",
603 "MFM_FULLY": "arbitrary bitwise masks"}
604 body += ["Masking:;%s\n" % masks[f["mask"]]]
605 body += ["Prerequisites:;%s\n" % f["prereqs"]]
606
607 access = {True: "read/write",
608 False: "read-only"}[f["writable"]]
609 body += ["Access:;%s\n" % access]
610
611 of10 = {None: "not supported",
612 "exact match": "yes (exact match only)",
613 "CIDR mask": "yes (CIDR match only)"}
614 body += ["OpenFlow 1.0:;%s\n" % of10[f["OF1.0"]]]
615
616 of11 = {None: "not supported",
617 "exact match": "yes (exact match only)",
618 "bitwise mask": "yes"}
619 body += ["OpenFlow 1.1:;%s\n" % of11[f["OF1.1"]]]
620
621 oxms = []
ffc317b9 622 for header, name, of_version_nr, ovs_version in [x for x in sorted(f['OXM'], key=lambda x: x[2]) if is_standard_oxm(x[1])]:
96fee5e0
BP
623 of_version = VERSION_REVERSE[of_version_nr]
624 oxms += [r"\fB%s\fR (%d) since OpenFlow %s and Open vSwitch %s" % (name, header[2], of_version, ovs_version)]
625 if not oxms:
626 oxms = ['none']
627 body += ['OXM:;T{\n%s\nT}\n' % r'\[char59] '.join(oxms)]
628
629 nxms = []
ffc317b9 630 for header, name, of_version_nr, ovs_version in [x for x in sorted(f['OXM'], key=lambda x: x[2]) if not is_standard_oxm(x[1])]:
96fee5e0
BP
631 nxms += [r"\fB%s\fR (%d) since Open vSwitch %s" % (name, header[2], ovs_version)]
632 if not nxms:
633 nxms = ['none']
634 body += ['NXM:;T{\n%s\nT}\n' % r'\[char59] '.join(nxms)]
635
636 body += [".TE\n"]
637
638 body += ['.PP\n']
639 body += [build.nroff.block_xml_to_nroff(field_node.childNodes)]
640
641def group_xml_to_nroff(group_node, fields):
642 title = group_node.attributes['title'].nodeValue
643
644 summary = []
645 body = []
646 for node in group_node.childNodes:
647 if node.nodeType == node.ELEMENT_NODE and node.tagName == 'field':
648 id_ = node.attributes['id'].nodeValue
649 field_to_xml(node, fields[id_], body, summary)
650 else:
651 body += [build.nroff.block_xml_to_nroff([node])]
652
653 content = [
654 '.bp\n',
655 '.SH \"%s\"\n' % build.nroff.text_to_nroff(title.upper() + " FIELDS"),
656 '.SS "Summary:"\n',
657 '.TS\n',
658 'tab(;);\n',
659 'l l l l l l l.\n',
660 'Name;Bytes;Mask;RW?;Prereqs;NXM/OXM Support\n',
661 '\_;\_;\_;\_;\_;\_\n']
662 content += summary
663 content += ['.TE\n']
664 content += body
665 return ''.join(content)
666
667def make_oxm_classes_xml(document):
668 s = '''tab(;);
669l l l.
670Prefix;Vendor;Class
671\_;\_;\_
672'''
673 for key in sorted(OXM_CLASSES, key=OXM_CLASSES.get):
ffc317b9 674 vendor, class_, class_type = OXM_CLASSES.get(key)
96fee5e0
BP
675 s += r"\fB%s\fR;" % key.rstrip('_')
676 if vendor:
677 s += r"\fL0x%08x\fR;" % vendor
678 else:
679 s += "(none);"
680 s += r"\fL0x%04x\fR;" % class_
681 s += "\n"
682 e = document.createElement('tbl')
683 e.appendChild(document.createTextNode(s))
684 return e
685
686def recursively_replace(node, name, replacement):
687 for child in node.childNodes:
688 if child.nodeType == node.ELEMENT_NODE:
689 if child.tagName == name:
690 node.replaceChild(replacement, child)
691 else:
692 recursively_replace(child, name, replacement)
693
694def make_ovs_fields(meta_flow_h, meta_flow_xml):
695 fields = extract_ofp_fields(meta_flow_h)
696 fields_map = {}
697 for f in fields:
698 fields_map[f['mff']] = f
699
700 document = xml.dom.minidom.parse(meta_flow_xml)
701 doc = document.documentElement
702
703 global version
704 if version == None:
705 version = "UNKNOWN"
706
707 print('''\
708'\\" tp
709.\\" -*- mode: troff; coding: utf-8 -*-
710.TH "ovs\-fields" 7 "%s" "Open vSwitch" "Open vSwitch Manual"
711.fp 5 L CR \\" Make fixed-width font available as \\fL.
712.de ST
713. PP
714. RS -0.15in
715. I "\\\\$1"
716. RE
717..
718
719.de SU
720. PP
721. I "\\\\$1"
722..
723
724.de IQ
725. br
726. ns
727. IP "\\\\$1"
728..
b404f002
BP
729
730.de TQ
731. br
732. ns
733. TP "\\\\$1"
734..
96fee5e0
BP
735.de URL
736\\\\$2 \\(laURL: \\\\$1 \\(ra\\\\$3
737..
738.if \\n[.g] .mso www.tmac
739.SH NAME
740ovs\-fields \- protocol header fields in OpenFlow and Open vSwitch
741.
742.PP
d34a1cc0 743''' % version)
96fee5e0
BP
744
745 recursively_replace(doc, 'oxm_classes', make_oxm_classes_xml(document))
746
747 s = ''
748 for node in doc.childNodes:
749 if node.nodeType == node.ELEMENT_NODE and node.tagName == "group":
750 s += group_xml_to_nroff(node, fields_map)
751 elif node.nodeType == node.TEXT_NODE:
752 assert node.data.isspace()
753 elif node.nodeType == node.COMMENT_NODE:
754 pass
755 else:
756 s += build.nroff.block_xml_to_nroff([node])
a4ce8b25 757
96fee5e0
BP
758 for f in fields:
759 if "used" not in f:
8889a8cc
BP
760 fatal("%s: field not documented "
761 "(please add documentation in lib/meta-flow.xml)"
762 % f["mff"])
96fee5e0
BP
763 if n_errors:
764 sys.exit(1)
a4ce8b25 765
96fee5e0
BP
766 output = []
767 for oline in s.split("\n"):
768 oline = oline.strip()
6e06e050
BP
769
770 # Life is easier with nroff if we don't try to feed it Unicode.
771 # Fortunately, we only use a few characters outside the ASCII range.
772 oline = oline.replace(u'\u2208', r'\[mo]')
773 oline = oline.replace(u'\u2260', r'\[!=]')
774 oline = oline.replace(u'\u2264', r'\[<=]')
775 oline = oline.replace(u'\u2265', r'\[>=]')
776 oline = oline.replace(u'\u00d7', r'\[mu]')
96fee5e0
BP
777 if len(oline):
778 output += [oline]
779
780 # nroff tends to ignore .bp requests if they come after .PP requests,
781 # so remove .PPs that precede .bp.
782 for i in range(len(output)):
783 if output[i] == '.bp':
784 j = i - 1
785 while j >= 0 and output[j] == '.PP':
786 output[j] = None
787 j -= 1
788 for i in range(len(output)):
789 if output[i] is not None:
790 print(output[i])
791\f
792## ------------ ##
793## Main Program ##
794## ------------ ##
795
796if __name__ == "__main__":
797 argv0 = sys.argv[0]
798 try:
799 options, args = getopt.gnu_getopt(sys.argv[1:], 'h',
800 ['help', 'ovs-version='])
52e4a477 801 except getopt.GetoptError as geo:
96fee5e0
BP
802 sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
803 sys.exit(1)
a4ce8b25 804
96fee5e0
BP
805 global version
806 version = None
807 for key, value in options:
808 if key in ['-h', '--help']:
809 usage()
810 elif key == '--ovs-version':
811 version = value
812 else:
813 sys.exit(0)
814
815 if not args:
816 sys.stderr.write("%s: missing command argument "
817 "(use --help for help)\n" % argv0)
a4ce8b25 818 sys.exit(1)
96fee5e0
BP
819
820 commands = {"meta-flow": (make_meta_flow, 1),
821 "nx-match": (make_nx_match, 1),
822 "ovs-fields": (make_ovs_fields, 2)}
823
824 if not args[0] in commands:
825 sys.stderr.write("%s: unknown command \"%s\" "
826 "(use --help for help)\n" % (argv0, args[0]))
178742f9 827 sys.exit(1)
96fee5e0
BP
828
829 func, n_args = commands[args[0]]
830 if len(args) - 1 != n_args:
831 sys.stderr.write("%s: \"%s\" requires %d arguments but %d "
832 "provided\n"
833 % (argv0, args[0], n_args, len(args) - 1))
834 sys.exit(1)
835
836 func(*args[1:])