netdev-dpdk: fix mbuf leaks
[cascardo/ovs.git] / build-aux / extract-ofp-errors
index 32d0913..16bfbc7 100755 (executable)
@@ -6,6 +6,15 @@ import re
 
 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,
+               "1.4": 0x05,
+               "1.5": 0x06}
+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]*"
@@ -13,12 +22,24 @@ tokenRe = "#?" + idRe + "|[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():
@@ -133,146 +154,205 @@ def usage():
     argv0 = os.path.basename(sys.argv[0])
     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.\
+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", "OF1.2", "OF1.3",
-                        "NX1.0", "NX1.1", "NX1.2", "NX1.3"):
+    for domain_name in version_map.values():
         domain[domain_name] = {}
         reverse[domain_name] = {}
 
     n_errors = 0
     expected_errors = {}
 
-    global fileName
-    for fileName in filenames:
-        global inputFile
-        global lineNumber
-        inputFile = open(fileName)
-        lineNumber = 0
+    open_file(fn)
 
-        while True:
-            getLine()
-            if re.match('enum ofperr', line):
-                break
+    while True:
+        getLine()
+        if re.match('enum ofperr', line):
+            break
 
-        while True:
+    while True:
+        getLine()
+        if line.startswith('/*') or not line or line.isspace():
+            continue
+        elif re.match('}', line):
+            break
+
+        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
-
-            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():
-                    fatal("unexpected syntax within error")
-                comment += ' %s' % line.lstrip('* \t').rstrip(' \t\r\n')
-            comment = comment[:-2].rstrip()
-
-            m = re.match('Expected: (.*)\.$', comment)
-            if m:
-                expected_errors[m.group(1)] = (fileName, lineNumber)
-                continue
+                fatal("unexpected syntax within error")
+            comment += ' %s' % line.lstrip('* \t').rstrip(' \t\r\n')
+        comment = comment[:-2].rstrip()
 
-            m = re.match('((?:.(?!\.  ))+.)\.  (.*)$', comment)
-            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")
 
-            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")
+        dsts, comment = m.groups()
 
-            enum = m.group(1)
+        getLine()
+        m = re.match('\s+(?:OFPERR_([A-Z0-9_]+))(\s*=\s*OFPERR_OFS)?,',
+                     line)
+        if not m:
+            fatal("syntax error expecting enum value")
 
-            comments.append(re.sub('\[[^]]*\]', '', comment))
-            names.append(enum)
+        enum = m.group(1)
+        if enum in names:
+            fatal("%s specified twice" % 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))
-                else:
-                    code = None
-
-                target_map = {"OF1.0+": ("OF1.0", "OF1.1", "OF1.2", "OF1.3"),
-                              "OF1.1+": ("OF1.1", "OF1.2", "OF1.3"),
-                              "OF1.2+": ("OF1.2", "OF1.3"),
-                              "OF1.3+": ("OF1.3",),
-                              "OF1.0":  ("OF1.0",),
-                              "OF1.1":  ("OF1.1",),
-                              "OF1.2":  ("OF1.2",),
-                              "OF1.3":  ("OF1.3",),
-                              "NX1.0+": ("OF1.0", "OF1.1", "OF1.2", "OF1.3"),
-                              "NX1.1+": ("OF1.1", "OF1.2", "OF1.3"),
-                              "NX1.2+": ("OF1.2", "OF1.3"),
-                              "NX1.3+": ("OF1.3",),
-                              "NX1.0":  ("OF1.0",),
-                              "NX1.1":  ("OF1.1",),
-                              "NX1.2":  ("OF1.2",),
-                              "NX1.3":  ("OF1.3",)}
-                if targets not in target_map:
-                    fatal("%s: unknown error domain" % targets)
-                if targets.startswith('NX') and code < 0x100:
-                    fatal("%s: NX domain code cannot be less than 0x100" % dst)
-                if targets.startswith('OF') and code >= 0x100:
-                    fatal("%s: OF domain code cannot be greater than 0x100"
-                          % dst)
-                for target in target_map[targets]:
-                    domain[target].setdefault(type_, {})
-                    if code in domain[target][type_]:
-                        msg = "%d,%d in %s means both %s and %s" % (
-                            type_, code, target,
-                            domain[target][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[target][type_][code][1],
-                                                domain[target][type_][code][2],
-                                                dst))
+        comments.append(re.sub('\[[^]]*\]', '', comment))
+        names.append(enum)
+
+        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['OF']:
+                # All standard OpenFlow errors have a type and a code.
+                if code is None:
+                    fatal("%s: %s domain requires code" % (dst, vendor_name))
+            elif vendor == vendor_map['NX']:
+                # Before OpenFlow 1.2, OVS used a Nicira extension to
+                # define errors that included a type and a code.
+                #
+                # In OpenFlow 1.2 and later, Nicira extension errors
+                # are defined using the OpenFlow experimenter error
+                # mechanism that includes a type but not a code.
+                if v1 < version_map['1.2'] or v2 < version_map['1.2']:
+                    if code is None:
+                        fatal("%s: NX1.0 and NX1.1 domains require code"
+                              % (dst, vendor_name))
+                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)
+            else:
+                # Experimenter extension error for OF1.2+ only.
+                if v1 < version_map['1.2']:
+                    fatal("%s: %s domain not supported before OF1.2"
+                          % (dst, vendor_name))
+                if code is not None:
+                    fatal("%s: %s domains do not have codes"
+                          % (dst, vendor_name))
+            if code is None:
+                code = 0
+
+            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:
-                        domain[target][type_][code] = (enum, fileName,
-                                                       lineNumber)
+                        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:
+                    domain[version][vendor][type_][code] = (enum, fileName,
+                                                   lineNumber)
 
-                    if enum in reverse[target]:
-                        error("%s: %s in %s means both %d,%d and %d,%d." %
-                              (dst, enum, target,
-                               reverse[target][enum][0],
-                               reverse[target][enum][1],
-                               type_, code))
-                    reverse[target][enum] = (type_, code)
+                assert enum not in reverse[version]
+                reverse[version][enum] = (vendor, type_, code)
 
-        inputFile.close()
+    inputFile.close()
 
     for fn, ln in expected_errors.values():
         sys.stderr.write("%s:%d: expected duplicate not used.\n" % (fn, ln))
@@ -289,8 +369,8 @@ def extract_ofp_errors(filenames):
 struct ofperr_domain {
     const char *name;
     uint8_t version;
-    enum ofperr (*decode)(uint16_t type, uint16_t code);
-    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] = {
@@ -308,21 +388,23 @@ static const char *error_comments[OFPERR_N_ERRORS] = {
     def output_domain(map, name, description, version):
         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]
-            if code is None:
-                continue
-            value = (type_ << 16) | code
+            vendor, type_, code = map[enum]
+            value = (vendor << 32) | (type_ << 16) | code
             if value in found:
                 continue
             found.add(value)
-            print ("    case (%d << 16) | %d:" % (type_, code))
+            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 ("""\
     }
@@ -338,27 +420,28 @@ static const struct ofperr_domain %s = {
     {""" % (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 ("        {       -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)
-    output_domain(reverse["OF1.2"], "ofperr_of12", "OpenFlow 1.2", 0x03)
-    output_domain(reverse["OF1.3"], "ofperr_of13", "OpenFlow 1.3", 0x04)
+    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])