macros = {}
+# Map from OpenFlow version number to version ID used in ofp_header.
+version_map = {"1.0": 0x01,
+ "1.1": 0x02,
+ "1.2": 0x03,
+ "1.3": 0x04}
+version_reverse_map = dict((v, k) for (k, v) in version_map.iteritems())
+
token = None
line = ""
idRe = "[a-zA-Z_][a-zA-Z_0-9]*"
inComment = False
inDirective = False
-def getLine():
+def open_file(fn):
+ global fileName
+ global inputFile
+ global lineNumber
+ fileName = fn
+ inputFile = open(fileName)
+ lineNumber = 0
+
+def tryGetLine():
+ global inputFile
global line
global lineNumber
line = inputFile.readline()
lineNumber += 1
- if line == "":
+ return line != ""
+
+def getLine():
+ if not tryGetLine():
fatal("unexpected end of input")
def getToken():
token = None
return False
-def fatal(msg):
+n_errors = 0
+def error(msg):
+ global n_errors
sys.stderr.write("%s:%d: %s\n" % (fileName, lineNumber, msg))
+ n_errors += 1
+
+def fatal(msg):
+ error(msg)
sys.exit(1)
def skipDirective():
return name
def print_enum(tag, constants, storage_class):
- print """
+ print ("""
%(storage_class)sconst char *
%(tag)s_to_string(uint16_t value)
{
switch (value) {\
""" % {"tag": tag,
"bufferlen": len(tag) + 32,
- "storage_class": storage_class}
+ "storage_class": storage_class})
for constant in constants:
- print " case %s: return \"%s\";" % (constant, constant)
- print """\
+ print (" case %s: return \"%s\";" % (constant, constant))
+ print ("""\
}
return NULL;
}\
-""" % {"tag": tag}
+""" % {"tag": tag})
def usage():
argv0 = os.path.basename(sys.argv[0])
- print '''\
+ print ('''\
%(argv0)s, for extracting OpenFlow error codes from header files
-usage: %(argv0)s FILE [FILE...]
+usage: %(argv0)s ERROR_HEADER VENDOR_HEADER
-This program reads the header files specified on the command line and
-outputs a C source file for translating OpenFlow error codes into
-strings, for use as lib/ofp-errors.c in the Open vSwitch source tree.
+This program reads VENDOR_HEADER to obtain OpenFlow vendor (aka
+experimenter IDs), then ERROR_HEADER to obtain OpenFlow error number.
+It outputs a C source file for translating OpenFlow error codes into
+strings.
-This program is specialized for reading lib/ofp-errors.h. It will not
-work on arbitrary header files without extensions.\
-''' % {"argv0": argv0}
+ERROR_HEADER should point to lib/ofp-errors.h.
+VENDOR_HEADER should point to include/openflow/openflow-common.h.
+The output is suitable for use as lib/ofp-errors.inc.\
+''' % {"argv0": argv0})
sys.exit(0)
-def extract_ofp_errors(filenames):
+def extract_vendor_ids(fn):
+ global vendor_map
+ vendor_map = {}
+ vendor_loc = {}
+
+ open_file(fn)
+ while tryGetLine():
+ m = re.match(r'#define\s+([A-Z0-9_]+)_VENDOR_ID\s+(0x[0-9a-fA-F]+|[0-9]+)', line)
+ if not m:
+ continue
+
+ name = m.group(1)
+ id_ = int(m.group(2), 0)
+
+ if name in vendor_map:
+ error("%s: duplicate definition of vendor" % name)
+ sys.stderr.write("%s: Here is the location of the previous "
+ "definition.\n" % vendor_loc[name])
+ sys.exit(1)
+
+ vendor_map[name] = id_
+ vendor_loc[name] = "%s:%d" % (fileName, lineNumber)
+
+ if not vendor_map:
+ fatal("%s: no vendor definitions found" % fn)
+
+ inputFile.close()
+
+ vendor_reverse_map = {}
+ for name, id_ in vendor_map.items():
+ if id_ in vendor_reverse_map:
+ fatal("%s: duplicate vendor id for vendors %s and %s"
+ % (id_, vendor_reverse_map[id_], name))
+ vendor_reverse_map[id_] = name
+
+def extract_ofp_errors(fn):
error_types = {}
comments = []
names = []
domain = {}
reverse = {}
- for domain_name in ("OF1.0", "OF1.1", "NX1.0", "NX1.1"):
+ for domain_name in version_map.values():
domain[domain_name] = {}
reverse[domain_name] = {}
- global fileName
- for fileName in filenames:
- global inputFile
- global lineNumber
- inputFile = open(fileName)
- lineNumber = 0
+ n_errors = 0
+ expected_errors = {}
- while True:
- getLine()
- if re.match('enum ofperr', line):
- break
+ open_file(fn)
+
+ while True:
+ getLine()
+ if re.match('enum ofperr', line):
+ break
+
+ while True:
+ getLine()
+ if line.startswith('/*') or not line or line.isspace():
+ continue
+ elif re.match('}', line):
+ break
- while True:
+ if not line.lstrip().startswith('/*'):
+ fatal("unexpected syntax between errors")
+
+ comment = line.lstrip()[2:].strip()
+ while not comment.endswith('*/'):
getLine()
if line.startswith('/*') or not line or line.isspace():
- continue
- elif re.match('}', line):
- break
+ fatal("unexpected syntax within error")
+ comment += ' %s' % line.lstrip('* \t').rstrip(' \t\r\n')
+ comment = comment[:-2].rstrip()
- m = re.match('\s+/\* ((?:.(?!\. ))+.)\. (.*)$', line)
- if not m:
- fatal("unexpected syntax between errors")
+ m = re.match('Expected: (.*)\.$', comment)
+ if m:
+ expected_errors[m.group(1)] = (fileName, lineNumber)
+ continue
- dsts, comment = m.groups()
+ m = re.match('((?:.(?!\. ))+.)\. (.*)$', comment)
+ if not m:
+ fatal("unexpected syntax between errors")
- comment.rstrip()
- while not comment.endswith('*/'):
- getLine()
- if line.startswith('/*') or not line or line.isspace():
- fatal("unexpected syntax within error")
- comment += ' %s' % line.lstrip('* \t').rstrip(' \t\r\n')
- comment = comment[:-2].rstrip()
+ dsts, comment = m.groups()
- getLine()
- m = re.match('\s+(?:OFPERR_((?:OFP|NX)[A-Z0-9_]+))(\s*=\s*OFPERR_OFS)?,',
- line)
- if not m:
- fatal("syntax error expecting enum value")
+ getLine()
+ m = re.match('\s+(?:OFPERR_([A-Z0-9_]+))(\s*=\s*OFPERR_OFS)?,',
+ line)
+ if not m:
+ fatal("syntax error expecting enum value")
- enum = m.group(1)
+ enum = m.group(1)
+ if enum in names:
+ fatal("%s specified twice" % enum)
- comments.append(comment)
- names.append(enum)
+ comments.append(re.sub('\[[^]]*\]', '', comment))
+ names.append(enum)
- for dst in dsts.split(', '):
- m = re.match(r'([A-Z0-9.+]+)\((\d+)(?:,(\d+))?\)$', dst)
- if not m:
- fatal("%s: syntax error in destination" % dst)
- targets = m.group(1)
- type_ = int(m.group(2))
- if m.group(3):
- code = int(m.group(3))
+ for dst in dsts.split(', '):
+ m = re.match(r'([A-Z]+)([0-9.]+)(\+|-[0-9.]+)?\((\d+)(?:,(\d+))?\)$', dst)
+ if not m:
+ fatal("%r: syntax error in destination" % dst)
+ vendor_name = m.group(1)
+ version1_name = m.group(2)
+ version2_name = m.group(3)
+ type_ = int(m.group(4))
+ if m.group(5):
+ code = int(m.group(5))
+ else:
+ code = None
+
+ if vendor_name not in vendor_map:
+ fatal("%s: unknown vendor" % vendor_name)
+ vendor = vendor_map[vendor_name]
+
+ if version1_name not in version_map:
+ fatal("%s: unknown OpenFlow version" % version1_name)
+ v1 = version_map[version1_name]
+
+ if version2_name is None:
+ v2 = v1
+ elif version2_name == "+":
+ v2 = max(version_map.values())
+ elif version2_name[1:] not in version_map:
+ fatal("%s: unknown OpenFlow version" % version2_name[1:])
+ else:
+ v2 = version_map[version2_name[1:]]
+
+ if v2 < v1:
+ fatal("%s%s: %s precedes %s"
+ % (version1_name, version2_name,
+ version2_name, version1_name))
+
+ if vendor == vendor_map['NX']:
+ if v1 >= version_map['1.2'] or v2 >= version_map['1.2']:
+ if code is not None:
+ fatal("%s: NX1.2+ domains do not have codes" % dst)
+ code = 0
+ elif vendor != vendor_map['OF']:
+ if code is not None:
+ fatal("%s: %s domains do not have codes" % vendor_name)
+
+ for version in range(v1, v2 + 1):
+ domain[version].setdefault(vendor, {})
+ domain[version][vendor].setdefault(type_, {})
+ if code in domain[version][vendor][type_]:
+ msg = "%#x,%d,%d in OF%s means both %s and %s" % (
+ vendor, type_, code, version_reverse_map[version],
+ domain[version][vendor][type_][code][0], enum)
+ if msg in expected_errors:
+ del expected_errors[msg]
+ else:
+ error("%s: %s." % (dst, msg))
+ sys.stderr.write("%s:%d: %s: Here is the location "
+ "of the previous definition.\n"
+ % (domain[version][vendor][type_][code][1],
+ domain[version][vendor][type_][code][2],
+ dst))
else:
- code = None
-
- target_map = {"OF1.0+": ("OF1.0", "OF1.1"),
- "OF1.1+": ("OF1.1",),
- "OF1.0": ("OF1.0",),
- "OF1.1": ("OF1.1",),
- "NX1.0+": ("OF1.0", "OF1.1"),
- "NX1.0": ("OF1.0",),
- "NX1.1": ("OF1.1",)}
- if targets not in target_map:
- fatal("%s: unknown error domain" % targets)
- for target in target_map[targets]:
- if type_ not in domain[target]:
- domain[target][type_] = {}
- if code in domain[target][type_]:
- fatal("%s: duplicate assignment in domain" % dst)
- domain[target][type_][code] = enum
- reverse[target][enum] = (type_, code)
-
- inputFile.close()
-
- print """\
+ domain[version][vendor][type_][code] = (enum, fileName,
+ lineNumber)
+
+ assert enum not in reverse[version]
+ reverse[version][enum] = (vendor, type_, code)
+
+ inputFile.close()
+
+ for fn, ln in expected_errors.values():
+ sys.stderr.write("%s:%d: expected duplicate not used.\n" % (fn, ln))
+ n_errors += 1
+
+ if n_errors:
+ sys.exit(1)
+
+ print ("""\
/* Generated automatically; do not modify! -*- buffer-read-only: t -*- */
#define OFPERR_N_ERRORS %d
struct ofperr_domain {
const char *name;
uint8_t version;
- enum ofperr (*decode)(uint16_t type, uint16_t code);
- enum ofperr (*decode_type)(uint16_t type);
- struct pair errors[OFPERR_N_ERRORS];
+ enum ofperr (*decode)(uint32_t vendor, uint16_t type, uint16_t code);
+ struct triplet errors[OFPERR_N_ERRORS];
};
static const char *error_names[OFPERR_N_ERRORS] = {
""" % (len(names),
'\n'.join(' "%s",' % name for name in names),
'\n'.join(' "%s",' % re.sub(r'(["\\])', r'\\\1', comment)
- for comment in comments))
+ for comment in comments)))
def output_domain(map, name, description, version):
- print """
+ print ("""
static enum ofperr
-%s_decode(uint16_t type, uint16_t code)
+%s_decode(uint32_t vendor, uint16_t type, uint16_t code)
{
- switch ((type << 16) | code) {""" % name
+ switch (((uint64_t) vendor << 32) | (type << 16) | code) {""" % name)
+ found = set()
for enum in names:
if enum not in map:
continue
- type_, code = map[enum]
+ vendor, type_, code = map[enum]
if code is None:
continue
- print " case (%d << 16) | %d:" % (type_, code)
- print " return OFPERR_%s;" % enum
- print """\
- }
-
- return 0;
-}
-
-static enum ofperr
-%s_decode_type(uint16_t type)
-{
- switch (type) {""" % name
- for enum in names:
- if enum not in map:
- continue
- type_, code = map[enum]
- if code is not None:
+ value = (vendor << 32) | (type_ << 16) | code
+ if value in found:
continue
- print " case %d:" % type_
- print " return OFPERR_%s;" % enum
- print """\
+ found.add(value)
+ if vendor:
+ vendor_s = "(%#xULL << 32) | " % vendor
+ else:
+ vendor_s = ""
+ print (" case %s(%d << 16) | %d:" % (vendor_s, type_, code))
+ print (" return OFPERR_%s;" % enum)
+ print ("""\
}
return 0;
-}"""
+}""")
- print """
-const struct ofperr_domain %s = {
+ print ("""
+static const struct ofperr_domain %s = {
"%s",
%d,
%s_decode,
- %s_decode_type,
- {""" % (name, description, version, name, name)
+ {""" % (name, description, version, name))
for enum in names:
if enum in map:
- type_, code = map[enum]
+ vendor, type_, code = map[enum]
if code == None:
code = -1
+ print " { %#8x, %2d, %3d }, /* %s */" % (vendor, type_, code, enum)
else:
- type_ = code = -1
- print " { %2d, %3d }, /* %s */" % (type_, code, enum)
- print """\
+ print (" { -1, -1, -1 }, /* %s */" % enum)
+ print ("""\
},
-};"""
+};""")
- output_domain(reverse["OF1.0"], "ofperr_of10", "OpenFlow 1.0", 0x01)
- output_domain(reverse["OF1.1"], "ofperr_of11", "OpenFlow 1.1", 0x02)
+ for version_name, id_ in version_map.items():
+ var = 'ofperr_of' + re.sub('[^A-Za-z0-9_]', '', version_name)
+ description = "OpenFlow %s" % version_name
+ output_domain(reverse[id_], var, description, id_)
if __name__ == '__main__':
if '--help' in sys.argv:
usage()
- elif len(sys.argv) < 2:
- sys.stderr.write("at least one non-option argument required; "
+ elif len(sys.argv) != 3:
+ sys.stderr.write("exactly two non-options arguments required; "
"use --help for help\n")
sys.exit(1)
else:
- extract_ofp_errors(sys.argv[1:])
+ extract_vendor_ids(sys.argv[2])
+ extract_ofp_errors(sys.argv[1])