Merge branch 'master' of github.com:ronaldchl/loxigen
diff --git a/Makefile b/Makefile
index fe5f19e..4712a04 100644
--- a/Makefile
+++ b/Makefile
@@ -106,10 +106,7 @@
 check-all: check check-c check-py check-java
 
 check:
-	./utest/test_parser.py
-	./utest/test_frontend.py
-	./utest/test_test_data.py
-	./utest/test_generic_utils.py
+	nosetests
 
 check-py: python
 	PYTHONPATH=${LOXI_OUTPUT_DIR}/pyloxi:. python py_gen/tests/generic_util.py
@@ -134,8 +131,10 @@
 install-java: java
 	cd ${OPENFLOWJ_WORKSPACE} && mvn install
 
-
 pylint:
 	pylint -E ${LOXI_PY_FILES}
 
+ctags:
+	ctags ${LOXI_PY_FILES} ${LOXI_TEMPLATE_FILES} ${INPUT_FILES} ${TEST_DATA}
+
 .PHONY: all clean debug check pylint c python
diff --git a/README.md b/README.md
index 1c7457f..1e70f9d 100644
--- a/README.md
+++ b/README.md
@@ -3,9 +3,33 @@
 
 LoxiGen is a tool that generates OpenFlow protocol libraries for a number of
 languages. It is composed of a frontend that parses wire protocol descriptions
-and a backend for each supported language (currently C and Python, with Java on
-the way).
+and a backend for each supported language (currently C, Python, and Java, with an 
+auto-generated wireshark dissector in Lua on the way). 
 
+LoxiGen currently supports OpenFlow Versions **1.0**, **1.1**, **1.2**, and **1.3.1**. Versions 1.0
+and 1.3.1 are actively used in production. Support for versions 1.1 and 1.2 is considered experimental.
+
+
+Prerequisites
+=============
+
+Running the unit tests requires [nosetests](http://nose.readthedocs.org/en/latest/).
+You can install it via easy_install,
+```
+easy_install nose
+```
+pip,
+```
+pip install nose
+```
+or via your distribution's package manager (example for Debian/Ubuntu):
+```
+sudo apt-get install python-nose
+```
+
+Nosetests is only required for running the unit tests, not for running LoxiGen
+itself. We do ask you to install it and use it before submitting pull requests,
+though.
 
 Usage
 =====
@@ -25,7 +49,9 @@
 make c
 ```
 
-The currently supported languages are `c` and `python`.
+The currently supported languages are `c`, `python` and `java`. There is an 
+experimental/partially completed backend that generates a lua wireshark dissector
+(`wireshark`).
 
 The generated libraries will be under the `loxi_output` directory. This can be
 changed with the `LOXI_OUTPUT_DIR` environment variable when using the Makefile.
@@ -39,3 +65,12 @@
 
 Please fork the repository on GitHub and send us a pull request. You might also
 be interested in the INTERNALS file which has notes about how LoxiGen works.
+
+Loxigen comes with a set of internal unit-tests, as well as with a set of tests
+for the generated artifacts. Be sure to run
+
+```
+make check-all
+```
+
+and correct any problems before submitting a pull request.
diff --git a/c_gen/build_of_g.py b/c_gen/build_of_g.py
new file mode 100755
index 0000000..9ea6d34
--- /dev/null
+++ b/c_gen/build_of_g.py
@@ -0,0 +1,559 @@
+# Copyright 2013, Big Switch Networks, Inc.
+#
+# LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
+# the following special exception:
+#
+# LOXI Exception
+#
+# As a special exception to the terms of the EPL, you may distribute libraries
+# generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
+# that copyright and licensing notices generated by LoxiGen are not altered or removed
+# from the LoxiGen Libraries and the notice provided below is (i) included in
+# the LoxiGen Libraries, if distributed in source code form and (ii) included in any
+# documentation for the LoxiGen Libraries, if distributed in binary form.
+#
+# Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
+#
+# You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
+# a copy of the EPL at:
+#
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# EPL for the specific language governing permissions and limitations
+# under the EPL.
+
+import sys
+
+import re
+import string
+import os
+import glob
+import copy
+import collections
+import c_gen.of_g_legacy as of_g
+import c_gen.type_maps as type_maps
+import c_gen.loxi_utils_legacy as loxi_utils
+import loxi_globals
+import c_gen.identifiers as identifiers
+import pyparsing
+import loxi_front_end.parser as parser
+import c_gen.translation as translation
+import loxi_front_end.frontend as frontend
+from loxi_ir import *
+from generic_utils import *
+
+root_dir = os.path.dirname(os.path.realpath(__file__))
+
+versions = {}
+# TODO:  Put these in a class so they get documented
+
+## Dict indexed by version giving all info related to version
+#
+# This is local; after processing, the information is stored in
+# of_g variables.
+
+def add_class(wire_version, cls, members):
+    """
+    Process a class for the given version and update the unified
+    list of classes as needed.
+
+    @param wire_version The wire version for this class defn
+    @param cls The name of the class being added
+    @param members The list of members with offsets calculated
+    """
+    memid = 0
+
+    sig = loxi_utils.class_signature(members)
+    if cls in of_g.unified:
+        uc = of_g.unified[cls]
+        if wire_version in uc:
+            debug("Error adding %s to unified. Wire ver %d exists" %
+                  (cls, wire_version))
+            sys.exit(1)
+        uc[wire_version] = {}
+        # Check for a matching signature
+        for wver in uc:
+            if type(wver) != type(0): continue
+            if wver == wire_version: continue
+            if not "use_version" in uc[wver]:
+                if sig == loxi_utils.class_signature(uc[wver]["members"]):
+                    log("Matched %s, ver %d to ver %d" %
+                          (cls, wire_version, wver))
+                    # have a match with existing version
+                    uc[wire_version]["use_version"] = wver
+                    # What else to do?
+                    return
+    else:  # Haven't seen this entry before
+        log("Adding %s to unified list, ver %d" % (cls, wire_version))
+        of_g.unified[cls] = dict(union={})
+        uc = of_g.unified[cls]
+
+    # At this point, need to add members for this version
+    uc[wire_version] = dict(members = members)
+
+    # Per member processing:
+    #  Add to union list (I'm sure there's a better way)
+    #  Check if it's a list
+    union = uc["union"]
+    if not cls in of_g.ordered_members:
+        of_g.ordered_members[cls] = []
+    for member in members:
+        m_name = member["name"]
+        m_type = member["m_type"]
+        if m_name.find("pad") == 0:
+            continue
+        if m_name in union:
+            if not m_type == union[m_name]["m_type"]:
+                debug("ERROR:   CLASS: %s. VERSION %d. MEMBER: %s. TYPE: %s" %
+                      (cls, wire_version, m_name, m_type))
+                debug("    Type conflict adding member to unified set.")
+                debug("    Current union[%s]:" % m_name)
+                debug(union[m_name])
+                sys.exit(1)
+        else:
+            union[m_name] = dict(m_type=m_type, memid=memid)
+            memid += 1
+        if not m_name in of_g.ordered_members[cls]:
+            of_g.ordered_members[cls].append(m_name)
+
+def update_offset(cls, wire_version, name, offset, m_type):
+    """
+    Update (and return) the offset based on type.
+    @param cls The parent class
+    @param wire_version The wire version being processed
+    @param name The name of the data member
+    @param offset The current offset
+    @param m_type The type declaration being processed
+    @returns A pair (next_offset, len_update)  next_offset is the new offset
+    of the next object or -1 if this is a var-length object.  len_update
+    is the increment that should be added to the length.  Note that (for
+    of_match_v3) it is variable length, but it adds 8 bytes to the fixed
+    length of the object
+    If offset is already -1, do not update
+    Otherwise map to base type and count and update (if possible)
+    """
+    if offset < 0:    # Don't update offset once set to -1
+        return offset, 0
+
+    count, base_type = loxi_utils.type_dec_to_count_base(m_type)
+
+    len_update = 0
+    if base_type in of_g.of_mixed_types:
+        base_type = of_g.of_mixed_types[base_type][wire_version]
+
+    base_class = base_type[:-2]
+    if (base_class, wire_version) in of_g.is_fixed_length:
+        bytes = of_g.base_length[(base_class, wire_version)]
+    else:
+        if base_type == "of_match_v3_t":
+            # This is a special case: it has non-zero min length
+            # but is variable length
+            bytes = -1
+            len_update = 8
+        elif base_type in of_g.of_base_types:
+            bytes = of_g.of_base_types[base_type]["bytes"]
+        else:
+            print "UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type)
+            log("UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type))
+            bytes = -1
+
+    # If bytes
+    if bytes > 0:
+        len_update = count * bytes
+
+    if bytes == -1:
+        return -1, len_update
+
+    return offset + (count * bytes), len_update
+
+def calculate_offsets_and_lengths(ordered_classes, classes, wire_version):
+    """
+    Generate the offsets for fixed offset class members
+    Also calculate the class_sizes when possible.
+
+    @param classes The classes to process
+    @param wire_version The wire version for this set of classes
+
+    Updates global variables
+    """
+
+    lists = set()
+
+    # Generate offsets
+    for cls in ordered_classes:
+        fixed_offset = 0 # The last "good" offset seen
+        offset = 0
+        last_offset = 0
+        last_name = "-"
+        for member in classes[cls]:
+            m_type = member["m_type"]
+            name = member["name"]
+            if last_offset == -1:
+                if name == "pad":
+                    log("Skipping pad for special offset for %s" % cls)
+                else:
+                    log("SPECIAL OFS: Member %s (prev %s), class %s ver %d" %
+                          (name, last_name, cls, wire_version))
+                    if (((cls, name) in of_g.special_offsets) and
+                        (of_g.special_offsets[(cls, name)] != last_name)):
+                        debug("ERROR: special offset prev name changed")
+                        debug("  cls %s. name %s. version %d. was %s. now %s" %
+                              cls, name, wire_version,
+                              of_g.special_offsets[(cls, name)], last_name)
+                        sys.exit(1)
+                    of_g.special_offsets[(cls, name)] = last_name
+
+            member["offset"] = offset
+            if m_type.find("list(") == 0:
+                (list_name, base_type) = loxi_utils.list_name_extract(m_type)
+                lists.add(list_name)
+                member["m_type"] = list_name + "_t"
+                offset = -1
+            elif m_type.find("struct") == 0:
+                debug("ERROR found struct: %s.%s " % (cls, name))
+                sys.exit(1)
+            elif m_type == "octets":
+                log("offset gen skipping octets: %s.%s " % (cls, name))
+                offset = -1
+            else:
+                offset, len_update = update_offset(cls, wire_version, name,
+                                                  offset, m_type)
+                if offset != -1:
+                    fixed_offset = offset
+                else:
+                    fixed_offset += len_update
+                    log("offset is -1 for %s.%s version %d " %
+                        (cls, name, wire_version))
+            last_offset = offset
+            last_name = name
+        of_g.base_length[(cls, wire_version)] = fixed_offset
+        if (offset != -1):
+            of_g.is_fixed_length.add((cls, wire_version))
+    for list_type in lists:
+        classes[list_type] = []
+        of_g.ordered_classes[wire_version].append(list_type)
+        of_g.base_length[(list_type, wire_version)] = 0
+
+def order_and_assign_object_ids():
+    """
+    Order all classes and assign object ids to all classes.
+
+    This is done to promote a reasonable order of the objects, putting
+    messages first followed by non-messages.  No assumptions should be
+    made about the order, nor about contiguous numbering.  However, the
+    numbers should all be reasonably small allowing arrays indexed by
+    these enum values to be defined.
+    """
+
+    # Generate separate message and non-message ordered lists
+    for cls in of_g.unified:
+        if loxi_utils.class_is_message(cls):
+            of_g.ordered_messages.append(cls)
+        elif loxi_utils.class_is_list(cls):
+            of_g.ordered_list_objects.append(cls)
+        else:
+            of_g.ordered_non_messages.append(cls)
+
+    of_g.ordered_messages.sort()
+    of_g.ordered_pseudo_objects.sort()
+    of_g.ordered_non_messages.sort()
+    of_g.ordered_list_objects.sort()
+    of_g.standard_class_order.extend(of_g.ordered_messages)
+    of_g.standard_class_order.extend(of_g.ordered_non_messages)
+    of_g.standard_class_order.extend(of_g.ordered_list_objects)
+
+    # This includes pseudo classes for which most code is not generated
+    of_g.all_class_order.extend(of_g.ordered_messages)
+    of_g.all_class_order.extend(of_g.ordered_non_messages)
+    of_g.all_class_order.extend(of_g.ordered_list_objects)
+    of_g.all_class_order.extend(of_g.ordered_pseudo_objects)
+
+    # Assign object IDs
+    for cls in of_g.ordered_messages:
+        of_g.unified[cls]["object_id"] = of_g.object_id
+        of_g.object_id += 1
+    for cls in of_g.ordered_non_messages:
+        of_g.unified[cls]["object_id"] = of_g.object_id
+        of_g.object_id += 1
+    for cls in of_g.ordered_list_objects:
+        of_g.unified[cls]["object_id"] = of_g.object_id
+        of_g.object_id += 1
+    for cls in of_g.ordered_pseudo_objects:
+        of_g.unified[cls] = {}
+        of_g.unified[cls]["object_id"] = of_g.object_id
+        of_g.object_id += 1
+
+
+def initialize_versions():
+    """
+    Create an empty datastructure for each target version.
+    """
+
+    for version in loxi_globals.OFVersions.target_versions:
+        wire_version = version.wire_version
+        version_name = of_g.of_version_wire2name[wire_version]
+        of_g.wire_ver_map[wire_version] = version_name
+        versions[version_name] = dict(
+            version_name = version_name,
+            wire_version = wire_version,
+            classes = {})
+        of_g.ordered_classes[wire_version] = []
+
+    of_g.target_version_list = [ v.wire_version for v in loxi_globals.OFVersions.target_versions ]
+
+def build_ordered_classes():
+    """
+    Read in from files given on command line and update global state
+
+    @fixme Should select versions to support from command line
+    """
+
+    for version, protocol in loxi_globals.ir.items():
+        wire_version = version.wire_version
+        # Populate global state
+        version_name = of_g.of_version_wire2name[wire_version]
+
+        for ofclass in protocol.classes:
+            if ofclass.name in ("of_group_add", "of_group_modify", "of_group_delete"):
+                continue
+            of_g.ordered_classes[wire_version].append(ofclass.name)
+            legacy_members = []
+            pad_count = 0
+            for m in ofclass.members:
+                if type(m) == OFPadMember:
+                    m_name = 'pad%d' % pad_count
+                    if m_name == 'pad0': m_name = 'pad'
+                    legacy_members.append(dict(m_type='uint8_t[%d]' % m.length,
+                                               name=m_name))
+                    pad_count += 1
+                else:
+                    # HACK the C backend does not yet support of_oxm_t
+                    if m.oftype == 'of_oxm_t':
+                        m_type = 'of_octets_t'
+                    else:
+                        enum = find(lambda e: e.name == m.oftype, protocol.enums)
+                        if enum and "wire_type" in enum.params:
+                            m_type = enum.params["wire_type"]
+                        else:
+                            m_type = m.oftype
+                    legacy_members.append(dict(m_type=m_type, name=m.name))
+            versions[version_name]['classes'][ofclass.name] = legacy_members
+
+        for enum in protocol.enums:
+            for entry in enum.entries:
+                identifiers.add_identifier(
+                    translation.loxi_name(entry.name),
+                    entry.name, enum.name, entry.value, wire_version,
+                    of_g.identifiers, of_g.identifiers_by_group)
+
+def populate_type_maps():
+    """
+    Use the type members in the IR to fill out the legacy type_maps.
+    """
+
+    def split_inherited_cls(cls):
+        if cls == 'of_meter_band_stats': # HACK not a subtype of of_meter_band
+            return None, None
+        for parent in sorted(type_maps.inheritance_data.keys(), reverse=True):
+            if cls.startswith(parent):
+                return (parent, cls[len(parent)+1:])
+        return None, None
+
+    def find_experimenter(parent, cls):
+        for experimenter in sorted(of_g.experimenter_name_to_id.keys(), reverse=True):
+            prefix = parent + '_' + experimenter
+            if cls.startswith(prefix) and cls != prefix:
+                return experimenter
+        return None
+
+    def find_type_value(ofclass, m_name):
+        for m in ofclass.members:
+            if isinstance(m, OFTypeMember) and m.name == m_name:
+                return m.value
+        raise KeyError("ver=%d, cls=%s, m_name=%s" % (wire_version, cls, m_name))
+
+    # Most inheritance classes: actions, instructions, etc
+    for version, protocol in loxi_globals.ir.items():
+        wire_version = version.wire_version
+        for ofclass in protocol.classes:
+            cls = ofclass.name
+            parent, subcls = split_inherited_cls(cls)
+            if not (parent and subcls):
+                continue
+            if parent == 'of_oxm':
+                type_len = find_type_value(ofclass, 'type_len')
+                oxm_class = (type_len >> 16) & 0xffff
+                if oxm_class != 0x8000:
+                    # Do not include experimenter OXMs in the main table
+                    val = type_maps.invalid_type
+                else:
+                    val = (type_len >> 8) & 0xff
+            else:
+                val = find_type_value(ofclass, 'type')
+            type_maps.inheritance_data[parent][wire_version][subcls] = val
+
+            # Extensions (only actions for now)
+            experimenter = find_experimenter(parent, cls)
+            if parent == 'of_action' and experimenter:
+                val = find_type_value(ofclass, 'subtype')
+                type_maps.extension_action_subtype[wire_version][experimenter][cls] = val
+                if wire_version >= of_g.VERSION_1_3:
+                    cls2 = parent + "_id" + cls[len(parent):]
+                    type_maps.extension_action_id_subtype[wire_version][experimenter][cls2] = val
+
+    # Messages
+    for version, protocol in loxi_globals.ir.items():
+        wire_version = version.wire_version
+        for ofclass in protocol.classes:
+            cls = ofclass.name
+            # HACK (though this is what loxi_utils.class_is_message() does)
+            if not [x for x in ofclass.members if isinstance(x, OFDataMember) and x.name == 'xid']:
+                continue
+            if type_maps.class_is_virtual(cls):
+                continue
+            # HACK hide of_group subclasses from legacy c backend
+            if ofclass.name in ("of_group_add", "of_group_modify", "of_group_delete"):
+                continue
+            subcls = cls[3:]
+            val = find_type_value(ofclass, 'type')
+            if not val in type_maps.message_types[wire_version].values():
+                type_maps.message_types[wire_version][subcls] = val
+
+            # Extensions
+            experimenter = find_experimenter('of', cls)
+            if experimenter and ofclass.is_subclassof("of_experimenter"):
+                val = find_type_value(ofclass, 'subtype')
+                type_maps.extension_message_subtype[wire_version][experimenter][cls] = val
+
+    type_maps.generate_maps()
+
+def analyze_input():
+    """
+    Add information computed from the input, including offsets and
+    lengths of struct members and the set of list and action_id types.
+    """
+
+    # Generate header classes for inheritance parents
+    for wire_version, ordered_classes in of_g.ordered_classes.items():
+        classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
+        for cls in ordered_classes:
+            if cls in type_maps.inheritance_map:
+                new_cls = cls + '_header'
+                of_g.ordered_classes[wire_version].append(new_cls)
+                classes[new_cls] = classes[cls]
+
+    # Generate action_id classes for OF 1.3
+    for wire_version, ordered_classes in of_g.ordered_classes.items():
+        if not wire_version in [of_g.VERSION_1_3]:
+            continue
+        classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
+        for cls in ordered_classes:
+            if not loxi_utils.class_is_action(cls):
+                continue
+            action = cls[10:]
+            if action == '' or action == 'header':
+                continue
+            name = "of_action_id_" + action
+            members = classes["of_action"][:]
+            of_g.ordered_classes[wire_version].append(name)
+            if type_maps.action_id_is_extension(name, wire_version):
+                # Copy the base action classes thru subtype
+                members = classes["of_action_" + action][:4]
+            classes[name] = members
+
+    # @fixme If we support extended actions in OF 1.3, need to add IDs
+    # for them here
+
+    for wire_version in of_g.wire_ver_map.keys():
+        version_name = of_g.of_version_wire2name[wire_version]
+        calculate_offsets_and_lengths(
+            of_g.ordered_classes[wire_version],
+            versions[version_name]['classes'],
+            wire_version)
+
+def unify_input():
+    """
+    Create Unified View of Objects
+    """
+
+    global versions
+
+    # Add classes to unified in wire-format order so that it is easier
+    # to generate things later
+    keys = versions.keys()
+    keys.sort(reverse=True)
+    for version in keys:
+        wire_version = versions[version]["wire_version"]
+        classes = versions[version]["classes"]
+        for cls in of_g.ordered_classes[wire_version]:
+            add_class(wire_version, cls, classes[cls])
+
+
+def log_all_class_info():
+    """
+    Log the results of processing the input
+
+    Debug function
+    """
+
+    for cls in of_g.unified:
+        for v in of_g.unified[cls]:
+            if type(v) == type(0):
+                log("cls: %s. ver: %d. base len %d. %s" %
+                    (str(cls), v, of_g.base_length[(cls, v)],
+                     loxi_utils.class_is_var_len(cls,v) and "not fixed"
+                     or "fixed"))
+                if "use_version" in of_g.unified[cls][v]:
+                    log("cls %s: v %d mapped to %d" % (str(cls), v,
+                           of_g.unified[cls][v]["use_version"]))
+                if "members" in of_g.unified[cls][v]:
+                    for member in of_g.unified[cls][v]["members"]:
+                        log("   %-20s: type %-20s. offset %3d" %
+                            (member["name"], member["m_type"],
+                             member["offset"]))
+
+def generate_all_files():
+    """
+    Create the files for the language target
+    """
+    for (name, fn) in lang_module.targets.items():
+        path = of_g.options.install_dir + '/' + name
+        os.system("mkdir -p %s" % os.path.dirname(path))
+        with open(path, "w") as outfile:
+            fn(outfile, os.path.basename(name))
+        print("Wrote contents for " + name)
+
+if __name__ == '__main__':
+    of_g.loxigen_log_file = open("loxigen.log", "w")
+    of_g.loxigen_dbg_file = sys.stdout
+
+    of_g.process_commandline()
+    # @fixme Use command line params to select log
+
+    if not config_sanity_check():
+        debug("Config sanity check failed\n")
+        sys.exit(1)
+
+    # Import the language file
+    lang_file = "lang_%s" % of_g.options.lang
+    lang_module = __import__(lang_file)
+
+    # If list files, just list auto-gen files to stdout and exit
+    if of_g.options.list_files:
+        for name in lang_module.targets:
+            print of_g.options.install_dir + '/' + name
+        sys.exit(0)
+
+    log("\nGenerating files for target language %s\n" % of_g.options.lang)
+
+    initialize_versions()
+    read_input()
+    populate_type_maps()
+    analyze_input()
+    unify_input()
+    order_and_assign_object_ids()
+    log_all_class_info()
+    generate_all_files()
diff --git a/c_gen/c_code_gen.py b/c_gen/c_code_gen.py
index 798f5a4..1106f2d 100644
--- a/c_gen/c_code_gen.py
+++ b/c_gen/c_code_gen.py
@@ -31,18 +31,17 @@
 """
 
 import sys
-import of_g
+import c_gen.of_g_legacy as of_g
 import c_match
 from generic_utils import *
-import c_gen.c_type_maps as c_type_maps
-import loxi_front_end.type_maps as type_maps
-import loxi_front_end.flags as flags
-import loxi_utils.loxi_utils as loxi_utils
-import loxi_front_end.identifiers as identifiers
+from c_gen import flags, type_maps, c_type_maps
+import c_gen.loxi_utils_legacy as loxi_utils
+from c_gen.loxi_utils_legacy import config_check
+
+import c_gen.identifiers as identifiers
 
 # 'property' is for queues. Could be trouble
 
-
 ################################################################
 #
 # Misc helper functions
diff --git a/c_gen/c_dump_gen.py b/c_gen/c_dump_gen.py
index 837ff78..a0af14e 100644
--- a/c_gen/c_dump_gen.py
+++ b/c_gen/c_dump_gen.py
@@ -33,13 +33,13 @@
 """
 
 import sys
-import of_g
-import loxi_front_end.match as match
-import loxi_front_end.flags as flags
+import c_gen.of_g_legacy as of_g
+import c_gen.match as match
+import c_gen.flags as flags
 from generic_utils import *
-import loxi_front_end.type_maps as type_maps
-import loxi_utils.loxi_utils as loxi_utils
-import loxi_front_end.identifiers as identifiers
+import c_gen.type_maps as type_maps
+import c_gen.loxi_utils_legacy as loxi_utils
+import c_gen.identifiers as identifiers
 from c_test_gen import var_name_map
 
 def gen_obj_dump_h(out, name):
diff --git a/c_gen/c_match.py b/c_gen/c_match.py
index 4001612..a45090a 100644
--- a/c_gen/c_match.py
+++ b/c_gen/c_match.py
@@ -40,8 +40,8 @@
 # takes mask
 
 import sys
-import of_g
-import loxi_front_end.match as match
+import c_gen.of_g_legacy as of_g
+import c_gen.match as match
 import c_code_gen
 
 def match_c_top_matter(out, name):
diff --git a/c_gen/c_show_gen.py b/c_gen/c_show_gen.py
index 0ec81b4..fc3edb8 100644
--- a/c_gen/c_show_gen.py
+++ b/c_gen/c_show_gen.py
@@ -33,13 +33,14 @@
 """
 
 import sys
-import of_g
-import loxi_front_end.match as match
-import loxi_front_end.flags as flags
+import c_gen.of_g_legacy as of_g
+import c_gen.match as match
+import c_gen.flags as flags
 from generic_utils import *
-import loxi_front_end.type_maps as type_maps
+import c_gen.type_maps as type_maps
 import loxi_utils.loxi_utils as loxi_utils
-import loxi_front_end.identifiers as identifiers
+import c_gen.loxi_utils_legacy as loxi_utils
+import c_gen.identifiers as identifiers
 from c_test_gen import var_name_map
 
 def gen_obj_show_h(out, name):
diff --git a/c_gen/c_test_gen.py b/c_gen/c_test_gen.py
index d237cfe..6068716 100644
--- a/c_gen/c_test_gen.py
+++ b/c_gen/c_test_gen.py
@@ -59,13 +59,13 @@
 """
 
 import sys
-import of_g
-import loxi_front_end.match as match
-import loxi_front_end.flags as flags
+import c_gen.of_g_legacy as of_g
+import c_gen.match as match
+import c_gen.flags as flags
 from generic_utils import *
-import loxi_front_end.type_maps as type_maps
-import loxi_utils.loxi_utils as loxi_utils
-import loxi_front_end.identifiers as identifiers
+import c_gen.type_maps as type_maps
+import c_gen.loxi_utils_legacy as loxi_utils
+import c_gen.identifiers as identifiers
 import util
 import test_data
 
@@ -128,6 +128,11 @@
             m_name == "experimenter" or
             m_name == "subtype")):
         return True
+
+    if (cls in ["of_bsn_lacp_stats_request", "of_bsn_lacp_stats_reply"] and (
+            m_name == "experimenter" or
+            m_name == "subtype")):
+        return True
     return loxi_utils.skip_member_name(m_name) or m_type not in scalar_types
 
 def gen_fill_string(out):
diff --git a/c_gen/c_type_maps.py b/c_gen/c_type_maps.py
index 8883193..024de52 100644
--- a/c_gen/c_type_maps.py
+++ b/c_gen/c_type_maps.py
@@ -29,10 +29,10 @@
 # @brief C code generation for LOXI type related maps
 #
 
-import of_g
+import c_gen.of_g_legacy as of_g
 import sys
 from generic_utils import *
-import loxi_front_end.type_maps as type_maps
+import c_gen.type_maps as type_maps
 
 
 # Some number larger than small type values, but less then
@@ -503,6 +503,7 @@
     uint16_t stats_type;
     uint16_t err_type;
     uint8_t flow_mod_cmd;
+    uint32_t experimenter, subtype;
 
     if (length < OF_MESSAGE_MIN_LENGTH) {
         return OF_OBJECT_INVALID;
@@ -541,10 +542,23 @@
             return OF_OBJECT_INVALID;
         }
         stats_type = of_message_stats_type_get(msg);
-        if (obj_id == OF_STATS_REQUEST) {
-            obj_id = of_stats_request_to_object_id(stats_type, ver);
+        if (stats_type == OF_STATS_TYPE_EXPERIMENTER) {
+            if (length < OF_MESSAGE_STATS_EXPERIMENTER_MIN_LENGTH) {
+                return OF_OBJECT_INVALID;
+            }
+            experimenter = of_message_stats_experimenter_id_get(msg);
+            subtype = of_message_stats_experimenter_subtype_get(msg);
+            if (obj_id == OF_STATS_REQUEST) {
+                obj_id = of_experimenter_stats_request_to_object_id(experimenter, subtype, ver);
+            } else {
+                obj_id = of_experimenter_stats_reply_to_object_id(experimenter, subtype, ver);
+            }
         } else {
-            obj_id = of_stats_reply_to_object_id(stats_type, ver);
+            if (obj_id == OF_STATS_REQUEST) {
+                obj_id = of_stats_request_to_object_id(stats_type, ver);
+            } else {
+                obj_id = of_stats_reply_to_object_id(stats_type, ver);
+            }
         }
     }
 
@@ -945,6 +959,9 @@
  * top level message: Action, instruction, error, stats, queue_props, oxm
  */
 #define OF_EXPERIMENTER_TYPE 0xffff
+
+int of_experimenter_stats_request_to_object_id(uint32_t experimenter, uint32_t subtype, int ver);
+int of_experimenter_stats_reply_to_object_id(uint32_t experimenter, uint32_t subtype, int ver);
 """)
     gen_type_to_obj_map_functions(out)
     gen_obj_to_type_map_functions(out)
@@ -1047,6 +1064,17 @@
     if ((type = of_object_to_stats_type(id, ver)) >= 0) {
         /* It's a stats obj */
         of_message_stats_type_set(msg, type);
+        if (type == OF_STATS_TYPE_EXPERIMENTER) {
+            switch (id) {
+            case OF_BSN_LACP_STATS_REQUEST:
+            case OF_BSN_LACP_STATS_REPLY:
+                of_message_stats_experimenter_id_set(msg, OF_EXPERIMENTER_ID_BSN);
+                of_message_stats_experimenter_subtype_set(msg, 1);
+                break;
+            default:
+                break;
+            }
+        }
     }
     if ((type = of_object_to_error_type(id, ver)) >= 0) {
         /* It's an error obj */
diff --git a/c_gen/c_validator_gen.py b/c_gen/c_validator_gen.py
index 2930724..126530a 100644
--- a/c_gen/c_validator_gen.py
+++ b/c_gen/c_validator_gen.py
@@ -33,13 +33,14 @@
 """
 
 import sys
-import of_g
-import loxi_front_end.match as match
-import loxi_front_end.flags as flags
+import c_gen.of_g_legacy as of_g
+import c_gen.match as match
+import c_gen.flags as flags
 from generic_utils import *
-import loxi_front_end.type_maps as type_maps
+import c_gen.type_maps as type_maps
 import loxi_utils.loxi_utils as loxi_utils
-import loxi_front_end.identifiers as identifiers
+import c_gen.loxi_utils_legacy as loxi_utils
+import c_gen.identifiers as identifiers
 from c_test_gen import var_name_map
 from c_code_gen import v3_match_offset_get
 
diff --git a/loxi_front_end/flags.py b/c_gen/flags.py
similarity index 98%
rename from loxi_front_end/flags.py
rename to c_gen/flags.py
index 47a7a7e..1fa4ae5 100644
--- a/loxi_front_end/flags.py
+++ b/c_gen/flags.py
@@ -36,7 +36,7 @@
 import sys
 import copy
 import type_maps
-import of_g
+import c_gen.of_g_legacy as of_g
 import re
 
 # These mark idents as _not_ flags and have precedence
@@ -73,4 +73,3 @@
             return True
 
     return False
-
diff --git a/loxi_front_end/identifiers.py b/c_gen/identifiers.py
similarity index 99%
rename from loxi_front_end/identifiers.py
rename to c_gen/identifiers.py
index 7f777ec..5862967 100644
--- a/loxi_front_end/identifiers.py
+++ b/c_gen/identifiers.py
@@ -31,7 +31,6 @@
 
 import sys
 from generic_utils import *
-import of_g
 
 ##
 # The value to use when an identifier is not defined for a version
diff --git a/c_gen/loci_utils.py b/c_gen/loci_utils.py
new file mode 100644
index 0000000..bc3092f
--- /dev/null
+++ b/c_gen/loci_utils.py
@@ -0,0 +1,267 @@
+import c_gen.of_g_legacy as of_g
+
+def class_signature(members):
+    """
+    Generate a signature string for a class in canonical form
+
+    @param cls The class whose signature is to be generated
+    """
+    return ";".join([",".join([x["m_type"], x["name"], str(x["offset"])])
+                     for x in members])
+
+def type_dec_to_count_base(m_type):
+    """
+    Resolve a type declaration like uint8_t[4] to a count (4) and base_type
+    (uint8_t)
+
+    @param m_type The string type declaration to process
+    """
+    count = 1
+    chk_ar = m_type.split('[')
+    if len(chk_ar) > 1:
+        count_str = chk_ar[1].split(']')[0]
+        if count_str in of_g.ofp_constants:
+            count = of_g.ofp_constants[count_str]
+        else:
+            count = int(count_str)
+        base_type = chk_ar[0]
+    else:
+        base_type = m_type
+    return count, base_type
+
+def list_to_entry_type(cls):
+    """
+    Return the entry type for a list
+    """
+    slen = len("of_list_")
+    return "of_" + cls[slen:]
+
+def type_to_short_name(m_type):
+    if m_type in of_g.of_base_types:
+        tname = of_g.of_base_types[m_type]["short_name"]
+    elif m_type in of_g.of_mixed_types:
+        tname = of_g.of_mixed_types[m_type]["short_name"]
+    else:
+        tname = "unknown"
+    return tname
+
+def type_to_name_type(cls, member_name):
+    """
+    Generate the root name of a member for accessor functions, etc
+    @param cls The class name
+    @param member_name The member name
+    """
+    members = of_g.unified[cls]["union"]
+    if not member_name in members:
+        debug("Error:  %s is not in class %s for acc_name defn" %
+              (member_name, cls))
+        os.exit()
+
+    mem = members[member_name]
+    m_type = mem["m_type"]
+    id = mem["memid"]
+    tname = type_to_short_name(m_type)
+
+    return "o%d_m%d_%s" % (of_g.unified[cls]["object_id"], id, tname)
+
+
+def member_to_index(m_name, members):
+    """
+    Given a member name, return the index in the members dict
+    @param m_name The name of the data member to search for
+    @param members The dict of members
+    @return Index if found, -1 not found
+
+    Note we could generate an index when processing the original input
+    """
+    count = 0
+    for d in members:
+        if d["name"] == m_name:
+            return count
+        count += 1
+    return -1
+
+def member_base_type(cls, m_name):
+    """
+    Map a member to its of_ type
+    @param cls The class name
+    @param m_name The name of the member being gotten
+    @return The of_ type of the member
+    """
+    rv = of_g.unified[cls]["union"][m_name]["m_type"]
+    if rv[-2:] == "_t":
+        return rv
+    return rv + "_t"
+
+def member_type_is_octets(cls, m_name):
+    return member_base_type(cls, m_name) == "of_octets_t"
+
+def member_returns_val(cls, m_name):
+    """
+    Should get accessor return a value rather than void
+    @param cls The class name
+    @param m_name The member name
+    @return True if of_g config and the specific member allow a
+    return value.  Otherwise False
+    """
+    m_type = of_g.unified[cls]["union"][m_name]["m_type"]
+    return (config_check("get_returns") =="value" and
+            m_type in of_g.of_scalar_types)
+
+def config_check(str, dictionary = of_g.code_gen_config):
+    """
+    Return config value if in dictionary; else return False.
+    @param str The lookup index
+    @param dictionary The dict to check; use code_gen_config if None
+    """
+
+    if str in dictionary:
+        return dictionary[str]
+
+    return False
+
+def h_file_to_define(name):
+    """
+    Convert a .h file name to the define used for the header
+    """
+    h_name = name[:-2].upper()
+    h_name = "_" + h_name + "_H_"
+    return h_name
+
+def type_to_cof_type(m_type):
+    if m_type in of_g.of_base_types:
+        if "cof_type" in of_g.of_base_types[m_type]:
+            return of_g.of_base_types[m_type]["cof_type"]
+    return m_type
+
+
+def member_is_scalar(cls, m_name):
+    return of_g.unified[cls]["union"][m_name]["m_type"] in of_g.of_scalar_types
+
+def type_is_scalar(m_type):
+    return m_type in of_g.of_scalar_types
+
+def skip_member_name(name):
+    return name.find("pad") == 0 or name in of_g.skip_members
+
+def enum_name(cls):
+    """
+    Return the name used for an enum identifier for the given class
+    @param cls The class name
+    """
+    return cls.upper()
+
+def class_in_version(cls, ver):
+    """
+    Return boolean indicating if cls is defined for wire version ver
+    """
+
+    return (cls, ver) in of_g.base_length
+
+def instance_to_class(instance, parent):
+    """
+    Return the name of the class for an instance of inheritance type parent
+    """
+    return parent + "_" + instance
+
+def sub_class_to_var_name(cls):
+    """
+    Given a subclass name like of_action_output, generate the
+    name of a variable like 'output'
+    @param cls The class name
+    """
+    pass
+
+def class_is_var_len(cls, version):
+    # Match is special case.  Only version 1.2 (wire version 3) is var
+    if cls == "of_match":
+        return version == 3
+
+    return not (cls, version) in of_g.is_fixed_length
+
+def base_type_to_length(base_type, version):
+    if base_type + "_t" in of_g.of_base_types:
+        inst_len = of_g.of_base_types[base_type + "_t"]["bytes"]
+    else:
+        inst_len = of_g.base_length[(base_type, version)]
+
+def version_to_name(version):
+    """
+    Convert an integer version to the C macro name
+    """
+    return "OF_" + of_g.version_names[version]
+
+##
+# Is class a flow modify of some sort?
+
+def cls_is_flow_mod(cls):
+    return cls in ["of_flow_mod", "of_flow_modify", "of_flow_add", "of_flow_delete",
+                   "of_flow_modify_strict", "of_flow_delete_strict"]
+
+def all_member_types_get(cls, version):
+    """
+    Get the members and list of types for members of a given class
+    @param cls The class name to process
+    @param version The version for the class
+    """
+    member_types = []
+
+    if not version in of_g.unified[cls]:
+        return ([], [])
+
+    if "use_version" in of_g.unified[cls][version]:
+        v = of_g.unified[cls][version]["use_version"]
+        members = of_g.unified[cls][v]["members"]
+    else:
+        members = of_g.unified[cls][version]["members"]
+    # Accumulate variables that are supported
+    for member in members:
+        m_type = member["m_type"]
+        m_name = member["name"]
+        if skip_member_name(m_name):
+            continue
+        if not m_type in member_types:
+            member_types.append(m_type)
+
+    return (members, member_types)
+
+def list_name_extract(list_type):
+    """
+    Return the base name for a list object of the given type
+    @param list_type The type of the list as appears in the input,
+    for example list(of_port_desc_t).
+    @return A pair, (list-name, base-type) where list-name is the
+    base name for the list, for example of_list_port_desc, and base-type
+    is the type of list elements like of_port_desc_t
+    """
+    base_type = list_type[5:-1]
+    list_name = base_type
+    if list_name.find("of_") == 0:
+        list_name = list_name[3:]
+    if list_name[-2:] == "_t":
+        list_name = list_name[:-2]
+    list_name = "of_list_" + list_name
+    return (list_name, base_type)
+
+def version_to_name(version):
+    """
+    Convert an integer version to the C macro name
+    """
+    return "OF_" + of_g.version_names[version]
+
+def gen_c_copy_license(out):
+    """
+    Generate the top comments for copyright and license
+    """
+    import c_gen.util
+    c_gen.util.render_template(out, '_copyright.c')
+
+def accessor_returns_error(a_type, m_type):
+    is_var_len = (not type_is_scalar(m_type)) and \
+        [x for x in of_g.of_version_range if class_is_var_len(m_type[:-2], x)] != []
+    if a_type == "set" and is_var_len:
+        return True
+    elif m_type == "of_match_t":
+        return True
+    else:
+        return False
diff --git a/c_gen/loxi_utils_legacy.py b/c_gen/loxi_utils_legacy.py
new file mode 100644
index 0000000..699006f
--- /dev/null
+++ b/c_gen/loxi_utils_legacy.py
@@ -0,0 +1,519 @@
+# Copyright 2013, Big Switch Networks, Inc.
+#
+# LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
+# the following special exception:
+#
+# LOXI Exception
+#
+# As a special exception to the terms of the EPL, you may distribute libraries
+# generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
+# that copyright and licensing notices generated by LoxiGen are not altered or removed
+# from the LoxiGen Libraries and the notice provided below is (i) included in
+# the LoxiGen Libraries, if distributed in source code form and (ii) included in any
+# documentation for the LoxiGen Libraries, if distributed in binary form.
+#
+# Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
+#
+# You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
+# a copy of the EPL at:
+#
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# EPL for the specific language governing permissions and limitations
+# under the EPL.
+
+"""
+@brief Utilities involving LOXI naming conventions
+
+Utility functions for OpenFlow class generation
+
+These may need to be sorted out into language specific functions
+"""
+
+import sys
+import c_gen.of_g_legacy as of_g
+import tenjin
+from generic_utils import find, memoize
+
+def class_signature(members):
+    """
+    Generate a signature string for a class in canonical form
+
+    @param cls The class whose signature is to be generated
+    """
+    return ";".join([",".join([x["m_type"], x["name"], str(x["offset"])])
+                     for x in members])
+
+def type_dec_to_count_base(m_type):
+    """
+    Resolve a type declaration like uint8_t[4] to a count (4) and base_type
+    (uint8_t)
+
+    @param m_type The string type declaration to process
+    """
+    count = 1
+    chk_ar = m_type.split('[')
+    if len(chk_ar) > 1:
+        count_str = chk_ar[1].split(']')[0]
+        if count_str in of_g.ofp_constants:
+            count = of_g.ofp_constants[count_str]
+        else:
+            count = int(count_str)
+        base_type = chk_ar[0]
+    else:
+        base_type = m_type
+    return count, base_type
+
+##
+# Class types:
+#
+# Virtual
+#    A virtual class is one which does not have an explicit wire
+#    representation.  For example, an inheritance super class
+#    or a list type.
+#
+# List
+#    A list of objects of some other type
+#
+# TLV16
+#    The wire represenation starts with 16-bit type and length fields
+#
+# OXM
+#    An extensible match object
+#
+# Message
+#    A top level OpenFlow message
+#
+#
+
+def class_is_message(cls):
+    """
+    Return True if cls is a message object based on info in unified
+    """
+    return "xid" in of_g.unified[cls]["union"] and cls != "of_header"
+
+def class_is_tlv16(cls):
+    """
+    Return True if cls_name is an object which uses uint16 for type and length
+    """
+    if cls.find("of_action") == 0: # Includes of_action_id classes
+        return True
+    if cls.find("of_instruction") == 0:
+        return True
+    if cls.find("of_queue_prop") == 0:
+        return True
+    if cls.find("of_table_feature_prop") == 0:
+        return True
+    # *sigh*
+    if cls.find("of_meter_band_stats") == 0:  # NOT A TLV
+        return False
+    if cls.find("of_meter_band") == 0:
+        return True
+    if cls.find("of_hello_elem") == 0:
+        return True
+    if cls == "of_match_v3":
+        return True
+    if cls == "of_match_v4":
+        return True
+    return False
+
+def class_is_u16_len(cls):
+    """
+    Return True if cls_name is an object which uses initial uint16 length
+    """
+    return cls in ["of_group_desc_stats_entry", "of_group_stats_entry",
+                   "of_flow_stats_entry", "of_bucket", "of_table_features"]
+
+def class_is_oxm(cls):
+    """
+    Return True if cls_name is an OXM object
+    """
+    if cls.find("of_oxm") == 0:
+        return True
+    return False
+
+def class_is_action(cls):
+    """
+    Return True if cls_name is an action object
+
+    Note that action_id is not an action object, though it has
+    the same header.  It looks like an action header, but the type
+    is used to identify a kind of action, it does not indicate the
+    type of the object following.
+    """
+    if cls.find("of_action_id") == 0:
+        return False
+    if cls.find("of_action") == 0:
+        return True
+
+    # For each vendor, check for vendor specific action
+    for exp in of_g.experimenter_name_to_id:
+        if cls.find("of_action" + exp) == 0:
+            return True
+
+    return False
+
+def class_is_action_id(cls):
+    """
+    Return True if cls_name is an action object
+
+    Note that action_id is not an action object, though it has
+    the same header.  It looks like an action header, but the type
+    is used to identify a kind of action, it does not indicate the
+    type of the object following.
+    """
+    if cls.find("of_action_id") == 0:
+        return True
+
+    # For each vendor, check for vendor specific action
+    for exp in of_g.experimenter_name_to_id:
+        if cls.find("of_action_id_" + exp) == 0:
+            return True
+
+    return False
+
+def class_is_instruction(cls):
+    """
+    Return True if cls_name is an instruction object
+    """
+    if cls.find("of_instruction") == 0:
+        return True
+
+    # For each vendor, check for vendor specific action
+    for exp in of_g.experimenter_name_to_id:
+        if cls.find("of_instruction_" + exp) == 0:
+            return True
+
+    return False
+
+def class_is_meter_band(cls):
+    """
+    Return True if cls_name is an instruction object
+    """
+    # meter_band_stats is not a member of meter_band class hierarchy
+    if cls.find("of_meter_band_stats") == 0:
+        return False
+    if cls.find("of_meter_band") == 0:
+        return True
+    return False
+
+def class_is_hello_elem(cls):
+    """
+    Return True if cls_name is an instruction object
+    """
+    if cls.find("of_hello_elem") == 0:
+        return True
+    return False
+
+def class_is_queue_prop(cls):
+    """
+    Return True if cls_name is a queue_prop object
+    """
+    if cls.find("of_queue_prop") == 0:
+        return True
+
+    # For each vendor, check for vendor specific action
+    for exp in of_g.experimenter_name_to_id:
+        if cls.find("of_queue_prop_" + exp) == 0:
+            return True
+
+    return False
+
+def class_is_table_feature_prop(cls):
+    """
+    Return True if cls_name is a queue_prop object
+    """
+    if cls.find("of_table_feature_prop") == 0:
+        return True
+    return False
+
+def class_is_stats_message(cls):
+    """
+    Return True if cls_name is a message object based on info in unified
+    """
+
+    return "stats_type" in of_g.unified[cls]["union"]
+
+def class_is_list(cls):
+    """
+    Return True if cls_name is a list object
+    """
+    return (cls.find("of_list_") == 0)
+
+def type_is_of_object(m_type):
+    """
+    Return True if m_type is an OF object type
+    """
+    # Remove _t from the type id and see if key for unified class
+    if m_type[-2:] == "_t":
+        m_type = m_type[:-2]
+    return m_type in of_g.unified
+
+def list_to_entry_type(cls):
+    """
+    Return the entry type for a list
+    """
+    slen = len("of_list_")
+    return "of_" + cls[slen:]
+
+def type_to_short_name(m_type):
+    if m_type in of_g.of_base_types:
+        tname = of_g.of_base_types[m_type]["short_name"]
+    elif m_type in of_g.of_mixed_types:
+        tname = of_g.of_mixed_types[m_type]["short_name"]
+    else:
+        tname = "unknown"
+    return tname
+
+def type_to_name_type(cls, member_name):
+    """
+    Generate the root name of a member for accessor functions, etc
+    @param cls The class name
+    @param member_name The member name
+    """
+    members = of_g.unified[cls]["union"]
+    if not member_name in members:
+        debug("Error:  %s is not in class %s for acc_name defn" %
+              (member_name, cls))
+        os.exit()
+
+    mem = members[member_name]
+    m_type = mem["m_type"]
+    id = mem["memid"]
+    tname = type_to_short_name(m_type)
+
+    return "o%d_m%d_%s" % (of_g.unified[cls]["object_id"], id, tname)
+
+
+def member_to_index(m_name, members):
+    """
+    Given a member name, return the index in the members dict
+    @param m_name The name of the data member to search for
+    @param members The dict of members
+    @return Index if found, -1 not found
+
+    Note we could generate an index when processing the original input
+    """
+    count = 0
+    for d in members:
+        if d["name"] == m_name:
+            return count
+        count += 1
+    return -1
+
+def member_base_type(cls, m_name):
+    """
+    Map a member to its of_ type
+    @param cls The class name
+    @param m_name The name of the member being gotten
+    @return The of_ type of the member
+    """
+    rv = of_g.unified[cls]["union"][m_name]["m_type"]
+    if rv[-2:] == "_t":
+        return rv
+    return rv + "_t"
+
+def member_type_is_octets(cls, m_name):
+    return member_base_type(cls, m_name) == "of_octets_t"
+
+def member_returns_val(cls, m_name):
+    """
+    Should get accessor return a value rather than void
+    @param cls The class name
+    @param m_name The member name
+    @return True if of_g config and the specific member allow a
+    return value.  Otherwise False
+    """
+    m_type = of_g.unified[cls]["union"][m_name]["m_type"]
+    return (config_check("get_returns") =="value" and
+            m_type in of_g.of_scalar_types)
+
+def config_check(str, dictionary = of_g.code_gen_config):
+    """
+    Return config value if in dictionary; else return False.
+    @param str The lookup index
+    @param dictionary The dict to check; use code_gen_config if None
+    """
+
+    if str in dictionary:
+        return dictionary[str]
+
+    return False
+
+def h_file_to_define(name):
+    """
+    Convert a .h file name to the define used for the header
+    """
+    h_name = name[:-2].upper()
+    h_name = "_" + h_name + "_H_"
+    return h_name
+
+def type_to_cof_type(m_type):
+    if m_type in of_g.of_base_types:
+        if "cof_type" in of_g.of_base_types[m_type]:
+            return of_g.of_base_types[m_type]["cof_type"]
+    return m_type
+
+
+def member_is_scalar(cls, m_name):
+    return of_g.unified[cls]["union"][m_name]["m_type"] in of_g.of_scalar_types
+
+def type_is_scalar(m_type):
+    return m_type in of_g.of_scalar_types
+
+def skip_member_name(name):
+    return name.find("pad") == 0 or name in of_g.skip_members
+
+def enum_name(cls):
+    """
+    Return the name used for an enum identifier for the given class
+    @param cls The class name
+    """
+    return cls.upper()
+
+def class_in_version(cls, ver):
+    """
+    Return boolean indicating if cls is defined for wire version ver
+    """
+
+    return (cls, ver) in of_g.base_length
+
+def instance_to_class(instance, parent):
+    """
+    Return the name of the class for an instance of inheritance type parent
+    """
+    return parent + "_" + instance
+
+def sub_class_to_var_name(cls):
+    """
+    Given a subclass name like of_action_output, generate the
+    name of a variable like 'output'
+    @param cls The class name
+    """
+    pass
+
+def class_is_var_len(cls, version):
+    # Match is special case.  Only version 1.2 (wire version 3) is var
+    if cls == "of_match":
+        return version == 3
+
+    return not (cls, version) in of_g.is_fixed_length
+
+def base_type_to_length(base_type, version):
+    if base_type + "_t" in of_g.of_base_types:
+        inst_len = of_g.of_base_types[base_type + "_t"]["bytes"]
+    else:
+        inst_len = of_g.base_length[(base_type, version)]
+
+def version_to_name(version):
+    """
+    Convert an integer version to the C macro name
+    """
+    return "OF_" + of_g.version_names[version]
+
+##
+# Is class a flow modify of some sort?
+
+def cls_is_flow_mod(cls):
+    return cls in ["of_flow_mod", "of_flow_modify", "of_flow_add", "of_flow_delete",
+                   "of_flow_modify_strict", "of_flow_delete_strict"]
+
+
+def all_member_types_get(cls, version):
+    """
+    Get the members and list of types for members of a given class
+    @param cls The class name to process
+    @param version The version for the class
+    """
+    member_types = []
+
+    if not version in of_g.unified[cls]:
+        return ([], [])
+
+    if "use_version" in of_g.unified[cls][version]:
+        v = of_g.unified[cls][version]["use_version"]
+        members = of_g.unified[cls][v]["members"]
+    else:
+        members = of_g.unified[cls][version]["members"]
+    # Accumulate variables that are supported
+    for member in members:
+        m_type = member["m_type"]
+        m_name = member["name"]
+        if skip_member_name(m_name):
+            continue
+        if not m_type in member_types:
+            member_types.append(m_type)
+
+    return (members, member_types)
+
+def list_name_extract(list_type):
+    """
+    Return the base name for a list object of the given type
+    @param list_type The type of the list as appears in the input,
+    for example list(of_port_desc_t).
+    @return A pair, (list-name, base-type) where list-name is the
+    base name for the list, for example of_list_port_desc, and base-type
+    is the type of list elements like of_port_desc_t
+    """
+    base_type = list_type[5:-1]
+    list_name = base_type
+    if list_name.find("of_") == 0:
+        list_name = list_name[3:]
+    if list_name[-2:] == "_t":
+        list_name = list_name[:-2]
+    list_name = "of_list_" + list_name
+    return (list_name, base_type)
+
+def version_to_name(version):
+    """
+    Convert an integer version to the C macro name
+    """
+    return "OF_" + of_g.version_names[version]
+
+def gen_c_copy_license(out):
+    """
+    Generate the top comments for copyright and license
+    """
+    import c_gen.util
+    c_gen.util.render_template(out, '_copyright.c')
+
+def accessor_returns_error(a_type, m_type):
+    is_var_len = (not type_is_scalar(m_type)) and \
+        [x for x in of_g.of_version_range if class_is_var_len(m_type[:-2], x)] != []
+    if a_type == "set" and is_var_len:
+        return True
+    elif m_type == "of_match_t":
+        return True
+    else:
+        return False
+
+def render_template(out, name, path, context, prefix = None):
+    """
+    Render a template using tenjin.
+    out: a file-like object
+    name: name of the template
+    path: array of directories to search for the template
+    context: dictionary of variables to pass to the template
+    prefix: optional prefix to use for embedding (for other languages than python)
+    """
+    pp = [ tenjin.PrefixedLinePreprocessor(prefix=prefix) if prefix else tenjin.PrefixedLinePreprocessor() ] # support "::" syntax
+    template_globals = { "to_str": str, "escape": str } # disable HTML escaping
+    engine = TemplateEngine(path=path, pp=pp)
+    out.write(engine.render(name, context, template_globals))
+
+def render_static(out, name, path):
+    """
+    Write out a static template.
+    out: a file-like object
+    name: name of the template
+    path: array of directories to search for the template
+    """
+    # Reuse the tenjin logic for finding the template
+    template_filename = tenjin.FileSystemLoader().find(name, path)
+    if not template_filename:
+        raise ValueError("template %s not found" % name)
+    with open(template_filename) as infile:
+        out.write(infile.read())
diff --git a/loxi_front_end/match.py b/c_gen/match.py
similarity index 98%
rename from loxi_front_end/match.py
rename to c_gen/match.py
index c715455..611a80d 100644
--- a/loxi_front_end/match.py
+++ b/c_gen/match.py
@@ -30,9 +30,9 @@
 # @fixme This still has lots of C specific code that should be moved into c_gen
 
 import sys
-import of_g
+import c_gen.of_g_legacy as of_g
 from generic_utils import *
-import loxi_utils.loxi_utils as loxi_utils
+import c_gen.loxi_utils_legacy as loxi_utils
 
 #
 # Use 1.2 match semantics for common case
@@ -556,5 +556,3 @@
             debug("Type mismatch for key %s in oxm data: %s vs %s" %
                   (key, of_match_members[key]["m_type"], oxm_type))
             sys.exit(1)
-
-
diff --git a/of_g.py b/c_gen/of_g_legacy.py
similarity index 85%
rename from of_g.py
rename to c_gen/of_g_legacy.py
index 7111259..adf5c2b 100644
--- a/of_g.py
+++ b/c_gen/of_g_legacy.py
@@ -32,7 +32,6 @@
 #
 
 import sys
-from optparse import OptionParser
 # @fixme Replace with argparse
 
 ################################################################
@@ -48,84 +47,9 @@
 wire_ver_map = {}
 
 ##
-# Command line options
-options = {}
-
-##
-# Command line arguments
-args = []
-
-##@var config_default
-# The default configuration dictionary for LOXI code generation
-options_default = {
-    "lang"               : "c",
-    "version-list"       : "1.0 1.1 1.2 1.3",
-    "install-dir"        : "loxi_output",
-}
-
-##
 # The list of wire versions which are to be supported
 target_version_list = []
 
-def lang_normalize(lang):
-    """
-    Normalize the representation of the language
-    """
-    return lang.lower()
-
-def version_list_normalize(vlist):
-    """
-    Normalize the version list and return as an array
-    """
-    out_list = []
-    # @fixme Map to OF version references
-    if vlist.find(',') > 0:
-        vlist = vlist.split(',')
-    else:
-        vlist = vlist.split()
-    vlist.sort()
-    for ver in vlist:
-        try:
-            out_list.append(of_param_version_map[ver])
-        except KeyError:
-            sys.stderr.write("Bad version input, %s" % str(ver))
-            sys.exit(1)
-
-    return out_list
-
-def process_commandline(default_vals=options_default):
-    """
-    Set up the options dictionary
-
-    @param cfg_dflt The default configuration dictionary
-    @return A pair (options, args) as per parser return
-    """
-    global options
-    global args
-    global target_version_list
-
-    parser = OptionParser(version="%prog 0.1")
-
-    #@todo Add options via dictionary
-    parser.add_option("--list-files", action="store_true", default=False,
-                      help="List output files generated")
-    parser.add_option("-l", "--lang", "--language",
-                      default=default_vals["lang"],
-                      help="Select the target language: c, python")
-    parser.add_option("-i", "--install-dir",
-                      default=default_vals["install-dir"],
-                      help="Directory to install generated files to (default %s)" % default_vals["install-dir"])
-    parser.add_option("-v", "--version-list",
-                      default=default_vals["version-list"],
-                      help="Specify the versions to target as 1.0 1.1 etc")
-
-    (options, args) = parser.parse_args()
-
-    options.lang = lang_normalize(options.lang)
-    target_version_list = version_list_normalize(options.version_list)
-    target_version_list.sort()
-    return (options, args)
-
 ##
 # The dictionary of config variables related to code
 #
@@ -352,12 +276,6 @@
     uint64_t metadata[(OF_OBJECT_METADATA_BYTES + 7) / 8];
 """
 
-# LOXI intermediate representation
-# This is a hash from wire versions to OFProtocol objects.
-# See loxi_ir.py
-
-ir = {}
-
 ##
 # LOXI identifiers
 #
diff --git a/c_gen/templates/locitest/test_validator.c b/c_gen/templates/locitest/test_validator.c
index 6b9dd87..a4b55eb 100644
--- a/c_gen/templates/locitest/test_validator.c
+++ b/c_gen/templates/locitest/test_validator.c
@@ -26,9 +26,9 @@
 :: # under the EPL.
 ::
 :: include('_copyright.c')
-:: import of_g
-:: from loxi_utils import loxi_utils
-:: from loxi_front_end import type_maps
+:: import c_gen.of_g_legacy as of_g
+:: import c_gen.loxi_utils_legacy as loxi_utils
+:: from c_gen import type_maps
 
 /**
  * Test message validator
diff --git a/c_gen/templates/of_message.h b/c_gen/templates/of_message.h
index 165fe51..c1b6785 100644
--- a/c_gen/templates/of_message.h
+++ b/c_gen/templates/of_message.h
@@ -58,6 +58,10 @@
 #define OF_MESSAGE_EXPERIMENTER_SUBTYPE_OFFSET 12
 #define OF_MESSAGE_EXPERIMENTER_MIN_LENGTH 16
 
+#define OF_MESSAGE_STATS_EXPERIMENTER_ID_OFFSET 16
+#define OF_MESSAGE_STATS_EXPERIMENTER_SUBTYPE_OFFSET 20
+#define OF_MESSAGE_STATS_EXPERIMENTER_MIN_LENGTH 24
+
 /**
  * The "default" free message function; NULL means use nominal malloc/free
  */
@@ -270,4 +274,42 @@
     }
 }
 
+/**
+ * @brief Get/set stats request/reply experimenter ID of a message
+ * @param msg Pointer to the message buffer of sufficient length
+ * @param experimenter_id Data for set operation
+ * @returns get returns experimenter id in host order
+ */
+
+static inline uint32_t
+of_message_stats_experimenter_id_get(of_message_t msg) {
+    uint32_t val;
+    buf_u32_get(msg + OF_MESSAGE_STATS_EXPERIMENTER_ID_OFFSET, &val);
+    return val;
+}
+
+static inline void
+of_message_stats_experimenter_id_set(of_message_t msg, uint32_t experimenter_id) {
+    buf_u32_set(msg + OF_MESSAGE_STATS_EXPERIMENTER_ID_OFFSET, experimenter_id);
+}
+
+/**
+ * @brief Get/set stats request/reply experimenter subtype of a message
+ * @param msg Pointer to the message buffer of sufficient length
+ * @param subtype Data for set operation
+ * @returns get returns experimenter subtype in host order
+ */
+
+static inline uint32_t
+of_message_stats_experimenter_subtype_get(of_message_t msg) {
+    uint32_t val;
+    buf_u32_get(msg + OF_MESSAGE_STATS_EXPERIMENTER_SUBTYPE_OFFSET, &val);
+    return val;
+}
+
+static inline void
+of_message_stats_experimenter_subtype_set(of_message_t msg, uint32_t subtype) {
+    buf_u32_set(msg + OF_MESSAGE_STATS_EXPERIMENTER_SUBTYPE_OFFSET, subtype);
+}
+
 #endif /* _OF_MESSAGE_H_ */
diff --git a/c_gen/templates/of_type_maps.c b/c_gen/templates/of_type_maps.c
index fabd25f..30c73ac 100644
--- a/c_gen/templates/of_type_maps.c
+++ b/c_gen/templates/of_type_maps.c
@@ -840,3 +840,27 @@
 
     return OF_ERROR_NONE;
 }
+
+int
+of_experimenter_stats_request_to_object_id(uint32_t experimenter, uint32_t subtype, int ver)
+{
+    switch (experimenter) {
+    case OF_EXPERIMENTER_ID_BSN:
+        switch (subtype) {
+        case 1: return OF_BSN_LACP_STATS_REQUEST;
+        }
+    }
+    return OF_OBJECT_INVALID;
+}
+
+int
+of_experimenter_stats_reply_to_object_id(uint32_t experimenter, uint32_t subtype, int ver)
+{
+    switch (experimenter) {
+    case OF_EXPERIMENTER_ID_BSN:
+        switch (subtype) {
+        case 1: return OF_BSN_LACP_STATS_REPLY;
+        }
+    }
+    return OF_OBJECT_INVALID;
+}
diff --git a/loxi_front_end/translation.py b/c_gen/translation.py
similarity index 99%
rename from loxi_front_end/translation.py
rename to c_gen/translation.py
index b1b3864..ef6c11b 100644
--- a/loxi_front_end/translation.py
+++ b/c_gen/translation.py
@@ -125,4 +125,3 @@
             if re.match(id_from, ident):
                 return re.sub(id_from, id_to, ident)
     return ident
-
diff --git a/loxi_front_end/type_maps.py b/c_gen/type_maps.py
similarity index 97%
rename from loxi_front_end/type_maps.py
rename to c_gen/type_maps.py
index a3394b2..cf88e35 100644
--- a/loxi_front_end/type_maps.py
+++ b/c_gen/type_maps.py
@@ -33,10 +33,11 @@
 # to wire value.
 #
 
-import of_g
+import c_gen.of_g_legacy as of_g
 import sys
 from generic_utils import *
 import loxi_utils.loxi_utils as loxi_utils
+import c_gen.loxi_utils_legacy as loxi_utils
 
 invalid_type = "invalid_type"
 invalid_value = "0xeeee"  # Note, as a string
@@ -155,7 +156,7 @@
     if loxi_utils.class_is_list(cls):
         return True
     # TODO get this from the input file when we have virtual class syntax
-    if cls in ["of_flow_mod", "of_stats_request", "of_stats_reply", "of_error_msg", "of_bsn_header", "of_nicira_header", "of_action_bsn", "of_action_nicira", "of_action_id_bsn", "of_action_id_nicira"]:
+    if cls in ["of_flow_mod", "of_stats_request", "of_stats_reply", "of_error_msg", "of_bsn_header", "of_nicira_header", "of_action_bsn", "of_action_nicira", "of_action_id_bsn", "of_action_id_nicira", "of_bsn_stats_request", "of_bsn_stats_reply", "of_experimenter_stats_request", "of_experimenter_stats_reply"]:
         return True
     return False
 
@@ -266,7 +267,8 @@
         meter_features = 11,
         table_features = 12,
         port_desc = 13,
-        experimenter = 0xffff
+        experimenter = 0xffff,
+        bsn_lacp = 0xffff
         )
     }
 
@@ -451,7 +453,9 @@
     "of_port_desc_stats_reply",
     "of_queue_stats_reply",
     "of_table_stats_reply",
-    "of_table_features_stats_reply"
+    "of_table_features_stats_reply",
+    "of_bsn_stats_reply",
+    "of_bsn_lacp_stats_reply",
 ]
 
 stats_request_list = [
@@ -469,7 +473,9 @@
     "of_port_desc_stats_request",
     "of_queue_stats_request",
     "of_table_stats_request",
-    "of_table_features_stats_request"
+    "of_table_features_stats_request",
+    "of_bsn_stats_request",
+    "of_bsn_lacp_stats_request",
 ]
 
 flow_mod_list = [
diff --git a/c_gen/util.py b/c_gen/util.py
index d4b25bf..54e0f80 100644
--- a/c_gen/util.py
+++ b/c_gen/util.py
@@ -30,12 +30,13 @@
 """
 import os
 import loxi_utils.loxi_utils as utils
+import template_utils as template_utils
 
 templates_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'templates')
 template_path = [templates_dir, templates_dir + '/locitest']
 
 def render_template(out, name, **context):
-    utils.render_template(out, name, template_path, context)
+    template_utils.render_template(out, name, template_path, context)
 
 def render_static(out, name):
-    utils.render_static(out, name, template_path)
+    template_utils.render_static(out, name, template_path)
diff --git a/cmdline.py b/cmdline.py
new file mode 100644
index 0000000..aafa019
--- /dev/null
+++ b/cmdline.py
@@ -0,0 +1,108 @@
+# Copyright 2013, Big Switch Networks, Inc.
+#
+# LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
+# the following special exception:
+#
+# LOXI Exception
+#
+# As a special exception to the terms of the EPL, you may distribute libraries
+# generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
+# that copyright and licensing notices generated by LoxiGen are not altered or removed
+# from the LoxiGen Libraries and the notice provided below is (i) included in
+# the LoxiGen Libraries, if distributed in source code form and (ii) included in any
+# documentation for the LoxiGen Libraries, if distributed in binary form.
+#
+# Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
+#
+# You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
+# a copy of the EPL at:
+#
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# EPL for the specific language governing permissions and limitations
+# under the EPL.
+
+from optparse import OptionParser
+
+from loxi_globals import OFVersions
+
+##
+# Command line options
+options = {}
+
+##
+# Command line arguments
+args = []
+
+##@var config_default
+# The default configuration dictionary for LOXI code generation
+options_default = {
+    "lang"               : "c",
+    "version-list"       : "1.0 1.1 1.2 1.3",
+    "install-dir"        : "loxi_output",
+}
+
+def lang_normalize(lang):
+    """
+    Normalize the representation of the language
+    """
+    return lang.lower()
+
+def version_list_normalize(vlist):
+    """
+    Normalize the version list and return as an array
+    """
+    out_list = []
+    # @fixme Map to OF version references
+    if vlist.find(',') > 0:
+        vlist = vlist.split(',')
+    else:
+        vlist = vlist.split()
+    vlist.sort()
+    for ver in vlist:
+        try:
+            out_list.append(OFVersions.from_string(ver))
+        except KeyError:
+            sys.stderr.write("Bad version input, %s" % str(ver))
+            sys.exit(1)
+    return out_list
+
+def process_commandline(default_vals=options_default):
+    """
+    Set up the options dictionary
+
+    @param cfg_dflt The default configuration dictionary
+    @return A pair (options, args) as per parser return
+    """
+    global options
+    global args
+    global target_version_list
+
+    parser = OptionParser(version="%prog 0.1")
+
+    #@todo Add options via dictionary
+    parser.add_option("--list-files", action="store_true", default=False,
+                      help="List output files generated")
+    parser.add_option("-l", "--lang", "--language",
+                      default=default_vals["lang"],
+                      help="Select the target language: c, python")
+    parser.add_option("-i", "--install-dir",
+                      default=default_vals["install-dir"],
+                      help="Directory to install generated files to (default %s)" % default_vals["install-dir"])
+    parser.add_option("-v", "--verbose",
+                      action="store_true", default=False,
+                      help="Debug output")
+
+    parser.add_option("-V", "--version-list",
+                      default=default_vals["version-list"],
+                      help="Specify the versions to target as 1.0 1.1 etc")
+
+    (options, args) = parser.parse_args()
+
+    options.lang = lang_normalize(options.lang)
+    target_version_list = version_list_normalize(options.version_list)
+    target_version_list.sort()
+    return (options, args, target_version_list)
diff --git a/generic_utils.py b/generic_utils.py
index ea9c589..1cfba86 100644
--- a/generic_utils.py
+++ b/generic_utils.py
@@ -30,30 +30,10 @@
 
 Intended to be imported into another namespace
 """
-
+import logging
 import collections
 import functools
 import sys
-import of_g
-
-
-################################################################
-#
-# Configuration related
-#
-################################################################
-
-def config_check(str, dictionary = of_g.code_gen_config):
-    """
-    Return config value if in dictionary; else return False.
-    @param str The lookup index
-    @param dictionary The dict to check; use code_gen_config if None
-    """
-
-    if str in dictionary:
-        return dictionary[str]
-
-    return False
 
 ################################################################
 #
@@ -63,18 +43,17 @@
 
 def debug(obj):
     """
-    Debug output to the current both the log file and debug output
-    @param out_str The stringified output to write
+    Legacy logging method. Delegate to logging.debug.
+    Use logging.debug directly in the future.
     """
-    of_g.loxigen_dbg_file.write(str(obj) + "\n")
-    log(obj)
+    logging.debug(obj)
 
 def log(obj):
     """
-    Log output to the current global log file
-    @param out_str The stringified output to write
+    Legacy logging method. Delegate to logging.info.
+    Use logging.info directly in the future.S
     """
-    of_g.loxigen_log_file.write(str(obj) + "\n")
+    logging.info(obj)
 
 ################################################################
 #
diff --git a/java_gen/codegen.py b/java_gen/codegen.py
index f349f9e..c01ba68 100644
--- a/java_gen/codegen.py
+++ b/java_gen/codegen.py
@@ -29,24 +29,28 @@
 @brief Main Java Generation module
 """
 
+import logging
 import pdb
 import os
 import shutil
 
-import of_g
+import loxi_globals
 from loxi_ir import *
 import lang_java
 import test_data
 from collections import namedtuple
 from import_cleaner import ImportCleaner
 
+import template_utils
 import loxi_utils.loxi_utils as loxi_utils
 
 import java_gen.java_model as java_model
 
-def gen_all_java():
-    basedir= '%s/openflowj' % of_g.options.install_dir
-    print "Outputting to %s" % basedir
+logger = logging.getLogger(__name__)
+
+def gen_all_java(install_dir):
+    basedir= '%s/openflowj' % install_dir
+    logger.info("Outputting to %s" % basedir)
     if os.path.exists(basedir):
         shutil.rmtree(basedir)
     os.makedirs(basedir)
@@ -81,16 +85,16 @@
         if not os.path.exists(dirname):
             os.makedirs(dirname)
         prefix = '//::(?=[ \t]|$)'
-        print "filename: %s" % filename
+        logger.debug("rendering filename: %s" % filename)
         with open(filename, "w") as f:
-            loxi_utils.render_template(f, template, [self.templates_dir], context, prefix=prefix)
+            template_utils.render_template(f, template, [self.templates_dir], context, prefix=prefix)
 
         try:
             cleaner = ImportCleaner(filename)
             cleaner.find_used_imports()
             cleaner.rewrite_file(filename)
         except:
-            print 'Cannot clean imports from file %s' % filename
+            logger.info('Cannot clean imports from file %s' % filename)
 
     def create_of_const_enums(self):
         for enum in self.java_model.enums:
@@ -100,7 +104,7 @@
                     template='const.java', enum=enum, all_versions=self.java_model.versions)
 
             for version in enum.versions:
-                clazz = java_model.OFGenericClass(package="org.projectfloodlight.openflow.protocol.ver{}".format(version.of_version), name="{}SerializerVer{}".format(enum.name, version.of_version))
+                clazz = java_model.OFGenericClass(package="org.projectfloodlight.openflow.protocol.ver{}".format(version.dotless_version), name="{}SerializerVer{}".format(enum.name, version.dotless_version))
 
                 if enum.is_bitmask:
                     self.render_class(clazz=clazz, template="const_set_serializer.java", enum=enum, version=version)
@@ -133,9 +137,9 @@
                                 template='of_virtual_class.java', version=java_class.version, msg=java_class,
                                 impl_class=java_class.name, model=self.java_model)
                         else:
-                            print "Class %s virtual but no discriminator" % java_class.name
+                            logger.warn("Class %s virtual but no discriminator" % java_class.name)
                 else:
-                    print "Class %s ignored by generate_class" % java_class.name
+                    logger.info("Class %s ignored by generate_class" % java_class.name)
 
     def create_unit_test(self, unit_tests):
         if unit_tests.has_test_data:
@@ -158,4 +162,4 @@
 def copy_prewrite_tree(basedir):
     """ Recursively copy the directory structure from ./java_gen/pre-write
        into $basedir"""
-    print "Copying pre-written files into %s" % basedir
+    logger.info("Copying pre-written files into %s" % basedir)
diff --git a/java_gen/java_model.py b/java_gen/java_model.py
index 83c89b5..824a320 100644
--- a/java_gen/java_model.py
+++ b/java_gen/java_model.py
@@ -36,16 +36,17 @@
 import re
 
 from generic_utils import find, memoize, OrderedSet, OrderedDefaultDict
-import of_g
+from loxi_globals import OFVersions
+import loxi_globals
 from loxi_ir import *
-import loxi_front_end.type_maps as type_maps
 import loxi_utils.loxi_utils as loxi_utils
-import py_gen.util as py_utils
 import test_data
 
 import java_gen.java_type as java_type
 from java_gen.java_type import erase_type_annotation
 
+logger = logging.getLogger(__name__)
+
 class JavaModel(object):
     # registry for enums that should not be generated
     # set(${java_enum_name})
@@ -60,7 +61,11 @@
     interface_blacklist = set( ("OFUint8", "OFUint32",))
     # registry of interface properties that should not be generated
     # map: $java_type -> set(java_name_property)
-    read_blacklist = defaultdict(lambda: set(), OFExperimenter=set(('data','subtype')), OFActionExperimenter=set(('data',)))
+    read_blacklist = defaultdict(lambda: set(),
+        OFExperimenter=set(('data','subtype')),
+        OFActionExperimenter=set(('data',)),
+        OFExperimenterStatsRequest=set(('data','subtype')),
+        OFExperimenterStatsReply=set(('data','subtype')))
     # map: $java_type -> set(java_name_property)
     write_blacklist = defaultdict(lambda: set(), OFOxm=set(('typeLen',)), OFAction=set(('type',)), OFInstruction=set(('type',)), OFFlowMod=set(('command', )), OFExperimenter=set(('data','subtype')), OFActionExperimenter=set(('data',)))
     # interfaces that are virtual
@@ -227,26 +232,12 @@
     @property
     @memoize
     def versions(self):
-        return OrderedSet( JavaOFVersion(raw_version) for raw_version in of_g.target_version_list )
+        return OrderedSet( JavaOFVersion(ir_version) for ir_version in OFVersions.target_versions)
 
     @property
     @memoize
     def interfaces(self):
-        version_map_per_class = collections.OrderedDict()
-
-        for raw_version, of_protocol in of_g.ir.items():
-            jversion = JavaOFVersion(of_protocol.wire_version)
-
-            for of_class in of_protocol.classes:
-                if not of_class.name in version_map_per_class:
-                    version_map_per_class[of_class.name] = collections.OrderedDict()
-
-                version_map_per_class[of_class.name][jversion] = of_class
-
-        interfaces = []
-        for class_name, version_map in version_map_per_class.items():
-            interfaces.append(JavaOFInterface(class_name, version_map))
-
+        interfaces = [ JavaOFInterface(ir_class) for ir_class in loxi_globals.unified.classes ]
         interfaces = [ i for i in interfaces if i.name not in self.interface_blacklist ]
 
         return interfaces
@@ -266,7 +257,8 @@
         name_version_enum_map = OrderedDefaultDict(lambda: OrderedDict())
 
         for version in self.versions:
-            of_protocol = of_g.ir[version.int_version]
+            logger.info("version: {}".format(version.ir_version))
+            of_protocol = loxi_globals.ir[version.ir_version]
             for enum in of_protocol.enums:
                 name_version_enum_map[enum.name][version] = enum
 
@@ -321,7 +313,7 @@
                         factory.members.append(i)
                         break
         return factories.values()
-    
+
     @memoize
     def factory_of(self, interface):
         for factory in self.of_factories:
@@ -355,8 +347,8 @@
     @property
     def factory_classes(self):
             return [ OFFactoryClass(
-                    package="org.projectfloodlight.openflow.protocol.ver{}".format(version.of_version),
-                    name="{}Ver{}".format(self.name, version.of_version),
+                    package="org.projectfloodlight.openflow.protocol.ver{}".format(version.dotless_version),
+                    name="{}Ver{}".format(self.name, version.dotless_version),
                     interface=self,
                     version=version
                     ) for version in model.versions ]
@@ -370,7 +362,7 @@
             return "build" + n[0].upper() + n[1:]
         else:
             return n
-    
+
     def of_version(self, version):
         for fc in self.factory_classes:
             if fc.version == version:
@@ -400,30 +392,32 @@
 class JavaOFVersion(object):
     """ Models a version of OpenFlow. contains methods to convert the internal
         Loxi version to a java constant / a string """
-    def __init__(self, int_version):
-        self.int_version = int(int_version)
+    def __init__(self, ir_version):
+        assert isinstance(ir_version, OFVersion)
+        self.ir_version = ir_version
+        self.int_version = self.ir_version.wire_version
 
     @property
-    def of_version(self):
-        return "1" + str(int(self.int_version) - 1)
+    def dotless_version(self):
+        return self.ir_version.version.replace(".", "")
 
     @property
     def constant_version(self):
-        return "OF_" + self.of_version
+        return "OF_" + self.dotless_version
 
     def __repr__(self):
         return "JavaOFVersion(%d)" % self.int_version
 
     def __str__(self):
-        return of_g.param_version_names[self.int_version]
+        return self.ir_version.version
 
     def __hash__(self):
-        return hash(self.int_version)
+        return hash(self.ir_version)
 
     def __eq__(self, other):
         if other is None or type(self) != type(other):
             return False
-        return (self.int_version,) == (other.int_version,)
+        return (self.ir_version,) == (other.ir_version,)
 
 #######################################################################
 ### Interface
@@ -433,20 +427,21 @@
     """ Models an OpenFlow Message class for the purpose of the java class.
         Version agnostic, in contrast to the loxi_ir python model.
     """
-    def __init__(self, c_name, version_map):
+    def __init__(self, ir_class):
         """"
         @param c_name: loxi style name (e.g., of_flow_add)
         @param version_map map of { JavaOFVersion: OFClass (from loxi_ir) }
         """
-        self.c_name = c_name
-        self.version_map = version_map
+        self.ir_class = ir_class
+        self.c_name = ir_class.name
+        self.version_map = { JavaOFVersion(v): c for v,c in ir_class.version_classes.items() }
         # name: the Java Type name, e.g., OFFlowAdd
-        self.name = java_type.name_c_to_caps_camel(c_name) if c_name != "of_header" else "OFMessage"
+        self.name = java_type.name_c_to_caps_camel(self.c_name) if self.c_name != "of_header" else "OFMessage"
         # variable_name name to use for variables of this type. i.e., flowAdd
         self.variable_name = self.name[2].lower() + self.name[3:]
         self.title_name = self.variable_name[0].upper() + self.variable_name[1:]
         # name for use in constants: FLOW_ADD
-        self.constant_name = c_name.upper().replace("OF_", "")
+        self.constant_name = self.c_name.upper().replace("OF_", "")
 
         pck_suffix, parent_interface, self.type_annotation = self.class_info()
         self.package = "org.projectfloodlight.openflow.protocol.%s" % pck_suffix if pck_suffix else "org.projectfloodlight.openflow.protocol"
@@ -510,30 +505,42 @@
         # inheritance information from the versioned lox_ir classes.
         if re.match(r'OFStatsRequest$', self.name):
             return ("", "OFMessage", "T extends OFStatsReply")
-        elif re.match(r'OF.+StatsRequest$', self.name):
-            return ("", "OFStatsRequest<{}>".format(re.sub(r'Request$', 'Reply', self.name)), None)
-        elif re.match(r'OF.+StatsReply$', self.name):
-            return ("", "OFStatsReply", None)
-        elif re.match(r'OF.+ErrorMsg$', self.name):
+        elif self.ir_class.is_subclassof('of_stats_request'):
+            if self.ir_class.is_subclassof('of_bsn_stats_request'):
+                return ("", "OFBsnStatsRequest", None)
+            elif self.ir_class.is_subclassof('of_experimenter_stats_request'):
+                return ("", "OFExperimenterStatsRequest", None)
+            else:
+                return ("", "OFStatsRequest<{}>".format(re.sub(r'Request$', 'Reply', self.name)), None)
+        elif self.ir_class.is_subclassof('of_stats_reply'):
+            if self.ir_class.is_subclassof('of_bsn_stats_reply'):
+                return ("", "OFBsnStatsReply", None)
+            elif self.ir_class.is_subclassof('of_experimenter_stats_reply'):
+                return ("", "OFExperimenterStatsReply", None)
+            else:
+                return ("", "OFStatsReply", None)
+        elif self.ir_class.is_subclassof('of_error_msg'):
             return ("", "OFErrorMsg", None)
-        elif re.match(r'OFFlow(Add|Modify(Strict)?|Delete(Strict)?)$', self.name):
+        elif self.ir_class.is_subclassof('of_flow_mod'):
             return ("", "OFFlowMod", None)
-        elif loxi_utils.class_is_message(self.c_name) and re.match(r'OFBsn.+$', self.name) and self.name != "OFBsnHeader":
+        elif self.ir_class.is_subclassof('of_group_mod'):
+            return ("", "OFGroupMod", None)
+        elif self.ir_class.is_subclassof('of_bsn_header'):
             return ("", "OFBsnHeader", None)
-        elif loxi_utils.class_is_message(self.c_name) and re.match(r'OFNicira.+$', self.name) and self.name != "OFNiciraHeader":
+        elif self.ir_class.is_subclassof('of_nicira_header'):
             return ("", "OFNiciraHeader", None)
-        elif self.name == "OFBsnHeader" or self.name =="OFNiciraHeader":
+        elif self.ir_class.is_subclassof('of_experimenter'):
             return ("", "OFExperimenter", None)
         elif re.match(r'OFMatch.*', self.name):
             return ("", "Match", None)
-        elif loxi_utils.class_is_message(self.c_name):
+        elif self.ir_class.is_message:
             return ("", "OFMessage", None)
-        elif loxi_utils.class_is_action(self.c_name):
-            if re.match(r'OFActionBsn.+', self.name):
+        elif self.ir_class.is_action:
+            if self.ir_class.is_subclassof('of_action_bsn'):
                 return ("action", "OFActionBsn", None)
-            elif re.match(r'OFActionNicira.+', self.name):
+            elif self.ir_class.is_subclassof('of_action_nicira'):
                 return ("action", "OFActionNicira", None)
-            elif self.name == "OFActionBsn" or self.name == "OFActionNicira":
+            elif self.ir_class.is_subclassof('of_action_experimenter'):
                 return ("action", "OFActionExperimenter", None)
             else:
                 return ("action", "OFAction", None)
@@ -560,6 +567,7 @@
             return ("", None, None)
 
     @property
+
     @memoize
     def writeable_members(self):
         return [ m for m in self.members if m.is_writeable ]
@@ -582,14 +590,30 @@
         all_versions = []
         member_map = collections.OrderedDict()
 
+        member_version_map = {}
         for (version, of_class) in self.version_map.items():
             for of_member in of_class.members:
                 if isinstance(of_member, OFLengthMember) or \
                    isinstance(of_member, OFFieldLengthMember) or \
                    isinstance(of_member, OFPadMember):
                     continue
+                java_member = JavaMember.for_of_member(self, of_member)
                 if of_member.name not in member_map:
-                    member_map[of_member.name] = JavaMember.for_of_member(self, of_member)
+                    member_map[of_member.name] = java_member
+                    member_version_map[of_member.name] = version
+                else:
+                    existing = member_map[of_member.name]
+
+                    if existing.java_type.public_type != java_member.java_type.public_type:
+                        raise Exception(
+                             "Error constructing interface {}: type signatures do not match up between versions.\n"
+                             " Member Name: {}\n"
+                             " Existing: Version={}, Java={}, IR={}\n"
+                             " New:      Version={}, Java={}, IR={}"
+                               .format(self.name, existing.name,
+                                   member_version_map[of_member.name], existing.java_type.public_type, existing.member.oftype,
+                                   version, java_member.java_type.public_type, java_member.member.oftype)
+                        )
 
         return tuple(m for m in member_map.values() if m.name not in model.read_blacklist[self.name])
 
@@ -604,7 +628,7 @@
                     JavaVirtualMember(self, "masked", java_type.boolean),
                     JavaVirtualMember(self, "canonical", java_type.make_oxm_jtype("T"))
                    ]
-        elif self.parent_interface and self.parent_interface.startswith("OFOxm"):
+        elif self.ir_class.is_subclassof("of_oxm"):
             field_type = java_type.make_match_field_jtype(model.oxm_map[self.name].type_name) \
                 if self.name in model.oxm_map \
                 else java_type.make_match_field_jtype()
@@ -616,7 +640,8 @@
                             custom_template=lambda builder: "OFOxm{}_getCanonical.java".format(".Builder" if builder else "")),
                    ]
             if not find(lambda x: x.name == "mask", self.ir_model_members):
-                virtual_members.append(JavaVirtualMember(self, "mask", find(lambda x: x.name == "value", self.ir_model_members).java_type))
+                virtual_members.append(
+                        JavaVirtualMember(self, "mask", find(lambda x: x.name == "value", self.ir_model_members).java_type))
 
         if not find(lambda m: m.name == "version", self.ir_model_members):
             virtual_members.append(JavaVirtualMember(self, "version", java_type.of_version))
@@ -670,7 +695,7 @@
         self.c_name = self.ir_class.name
         self.version = version
         self.constant_name = self.c_name.upper().replace("OF_", "")
-        self.package = "org.projectfloodlight.openflow.protocol.ver%s" % version.of_version
+        self.package = "org.projectfloodlight.openflow.protocol.ver%s" % version.dotless_version
         self.generated = False
 
     @property
@@ -680,7 +705,7 @@
 
     @property
     def name(self):
-        return "%sVer%s" % (self.interface.name, self.version.of_version)
+        return "%sVer%s" % (self.interface.name, self.version.dotless_version)
 
     @property
     def variable_name(self):
@@ -696,23 +721,16 @@
     @property
     def min_length(self):
         """ @return the minimum wire length of an instance of this class in bytes """
-        id_tuple = (self.ir_class.name, self.version.int_version)
-        return of_g.base_length[id_tuple] if id_tuple in of_g.base_length else -1
+        return self.ir_class.base_length
 
     @property
     def is_fixed_length(self):
         """ true iff this class serializes to a fixed length on the wire """
-        return (self.ir_class.name, self.version.int_version) in of_g.is_fixed_length and \
-                not self.is_virtual
+        return self.ir_class.is_fixed_length and not self.is_virtual
 
     def all_properties(self):
         return self.interface.members
 
-    def get_member(self, name):
-        for m in self.members:
-            if m.name == name:
-                return m
-
     @property
     @memoize
     def data_members(self):
@@ -802,7 +820,8 @@
     @property
     @memoize
     def subclasses(self):
-        return [ c for c in model.all_classes if c.version == self.version and c.ir_class.superclass == self.c_name ]
+        return [ c for c in model.all_classes if c.version == self.version and c.ir_class.superclass
+                   and c.ir_class.superclass.name == self.c_name ]
 
 #######################################################################
 ### Member
@@ -957,16 +976,8 @@
 
     @property
     def is_universal(self):
-        if not self.msg.c_name in of_g.unified:
-            print("%s not self.unified" % self.msg.c_name)
-            return False
-        for version in of_g.unified[self.msg.c_name]:
-            if version == 'union' or version =='object_id':
-                continue
-            if 'use_version' in of_g.unified[self.msg.c_name][version]:
-                continue
-
-            if not self.member.name in (f['name'] for f in of_g.unified[self.msg.c_name][version]['members']):
+        for version, ir_class in self.msg.ir_class.version_classes.items():
+            if not ir_class.member_by_name(self.member.name):
                 return False
         return True
 
@@ -1022,9 +1033,9 @@
 class JavaUnitTestSet(object):
     def __init__(self, java_class):
         self.java_class = java_class
-        first_data_file_name = "of{version}/{name}.data".format(version=java_class.version.of_version,
+        first_data_file_name = "of{version}/{name}.data".format(version=java_class.version.dotless_version,
                                                      name=java_class.c_name[3:])
-        glob_file_name = "of{version}/{name}__*.data".format(version=java_class.version.of_version,
+        glob_file_name = "of{version}/{name}__*.data".format(version=java_class.version.dotless_version,
                                                      name=java_class.c_name[3:])
         test_class_name = self.java_class.name + "Test"
         self.test_units = []
@@ -1062,7 +1073,7 @@
     def __init__(self, java_class, file_name=None, test_class_name=None):
         self.java_class = java_class
         if file_name is None:
-            self.data_file_name = "of{version}/{name}.data".format(version=java_class.version.of_version,
+            self.data_file_name = "of{version}/{name}.data".format(version=java_class.version.dotless_version,
                                                          name=java_class.c_name[3:])
         else:
             self.data_file_name = file_name
diff --git a/java_gen/java_type.py b/java_gen/java_type.py
index 333efd8..d1a9e67 100644
--- a/java_gen/java_type.py
+++ b/java_gen/java_type.py
@@ -4,9 +4,9 @@
 import subprocess
 import time
 
+import loxi_globals
 from generic_utils import memoize
 import loxi_utils.loxi_utils as loxi_utils
-import of_g
 
 def erase_type_annotation(class_name):
     m=re.match(r'(.*)<.*>', class_name)
@@ -145,7 +145,7 @@
         ver = ANY if version is None else version.int_version
 
         if not "version" in arguments:
-            arguments["version"] = version.of_version
+            arguments["version"] = version.dotless_version
 
         def lookup(ver, pub_type):
             if (ver, pub_type) in self.ops:
@@ -328,6 +328,8 @@
         .op(read='ChannelUtilsVer$version.readOFMatch(bb)', \
             write='$name.writeTo(bb)',
             default="OFFactoryVer$version.MATCH_WILDCARD_ALL");
+group_mod_cmd = JType('OFGroupModCommand', 'short') \
+        .op(version=ANY, read="bb.readShort()", write="bb.writeShort($name)")
 flow_mod_cmd = JType('OFFlowModCommand', 'short') \
         .op(version=1, read="bb.readShort()", write="bb.writeShort($name)") \
         .op(version=ANY, read="bb.readByte()", write="bb.writeByte($name)")
@@ -603,18 +605,22 @@
         'of_action_set_tp_src': { 'tp_port': transport_port },
         'of_action_set_vlan_pcp': { 'vlan_pcp': vlan_pcp },
         'of_action_set_vlan_vid': { 'vlan_vid': vlan_vid },
+
+        'of_group_mod' : { 'command' : group_mod_cmd },
+        'of_group_add' : { 'command' : group_mod_cmd },
+        'of_group_modify' : { 'command' : group_mod_cmd },
+        'of_group_delete' : { 'command' : group_mod_cmd },
+
+        'of_bucket' : { 'watch_group': of_group },
 }
 
 
 @memoize
 def enum_java_types():
     enum_types = {}
-
-    for protocol in of_g.ir.values():
-        for enum in protocol.enums:
-            java_name = name_c_to_caps_camel(re.sub(r'_t$', "", enum.name))
-
-            enum_types[enum.name] = gen_enum_jtype(java_name, enum.is_bitmask)
+    for enum in loxi_globals.unified.enums:
+        java_name = name_c_to_caps_camel(re.sub(r'_t$', "", enum.name))
+        enum_types[enum.name] = gen_enum_jtype(java_name, enum.is_bitmask)
     return enum_types
 
 def make_match_field_jtype(sub_type_name="?"):
diff --git a/java_gen/pre-written/pom.xml b/java_gen/pre-written/pom.xml
index e516b6e..cc480df 100644
--- a/java_gen/pre-written/pom.xml
+++ b/java_gen/pre-written/pom.xml
@@ -60,6 +60,16 @@
             <artifactId>slf4j-api</artifactId>
             <version>1.7.5</version>
         </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-core</artifactId>
+            <version>1.0.13</version>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <version>1.0.13</version>
+        </dependency>
     </dependencies>
     <build>
         <plugins>
diff --git a/java_gen/pre-written/src/test/resources/logback-test.xml b/java_gen/pre-written/src/test/resources/logback-test.xml
new file mode 100644
index 0000000..e759962
--- /dev/null
+++ b/java_gen/pre-written/src/test/resources/logback-test.xml
@@ -0,0 +1,13 @@
+<configuration scan="true">
+  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+    <encoder>
+      <pattern>%d{HH:mm:ss.SSS} %level [%logger{20}:%thread] %msg%n</pattern>
+    </encoder>
+  </appender>
+  <root level="INFO">
+    <appender-ref ref="STDOUT" />
+  </root>
+  <logger name="org" level="WARN"/>
+  <logger name="LogService" level="WARN"/> <!-- Restlet access logging -->
+  <logger name="org.projectfloodlight.openflow" level="DEBUG"/>
+</configuration>
diff --git a/java_gen/templates/_field_accessors.java b/java_gen/templates/_field_accessors.java
index 030388c..41ac66f 100644
--- a/java_gen/templates/_field_accessors.java
+++ b/java_gen/templates/_field_accessors.java
@@ -11,7 +11,7 @@
     @Override
     public ${prop.java_type.public_type} ${prop.getter_name}()${ "" if prop in msg.members else "throws UnsupportedOperationException"} {
 //:: if prop in msg.members:
-//::    version_prop = msg.get_member(prop.name)
+//::    version_prop = msg.member_by_name(prop.name)
 //::    if version_prop.is_fixed_value:
         return ${version_prop.enum_value};
 //::    elif version_prop.is_length_value:
diff --git a/java_gen/templates/const.java b/java_gen/templates/const.java
index 413c946..a7786f4 100644
--- a/java_gen/templates/const.java
+++ b/java_gen/templates/const.java
@@ -26,7 +26,6 @@
 //:: # under the EPL.
 //::
 //:: import itertools
-//:: import of_g
 //:: include('_copyright.java')
 
 //:: include('_autogen.java')
diff --git a/java_gen/templates/const_serializer.java b/java_gen/templates/const_serializer.java
index 12ff28a..03ca8ac 100644
--- a/java_gen/templates/const_serializer.java
+++ b/java_gen/templates/const_serializer.java
@@ -26,7 +26,6 @@
 //:: # under the EPL.
 //::
 //:: import itertools
-//:: import of_g
 //:: include('_copyright.java')
 
 //:: include('_autogen.java')
diff --git a/java_gen/templates/const_set_serializer.java b/java_gen/templates/const_set_serializer.java
index 4c624ee..61592a6 100644
--- a/java_gen/templates/const_set_serializer.java
+++ b/java_gen/templates/const_set_serializer.java
@@ -26,7 +26,6 @@
 //:: # under the EPL.
 //::
 //:: import itertools
-//:: import of_g
 //:: include('_copyright.java')
 
 //:: include('_autogen.java')
diff --git a/java_gen/templates/of_class.java b/java_gen/templates/of_class.java
index 36d407c..4057b3d 100644
--- a/java_gen/templates/of_class.java
+++ b/java_gen/templates/of_class.java
@@ -28,7 +28,6 @@
 //:: from loxi_ir import *
 //:: import os
 //:: import itertools
-//:: import of_g
 //:: include('_copyright.java')
 
 //:: include('_autogen.java')
@@ -50,6 +49,9 @@
 //:: #endif
 
 //:: for prop in msg.data_members:
+    //:: if prop.java_type.public_type != msg.interface.member_by_name(prop.name).java_type.public_type:
+    //::    raise Exception("Interface and Class types do not match up: C: {} <-> I: {}".format(prop.java_type.public_type, msg.interface.member_by_name(prop.name).java_type.public_type))
+    //:: #endif
     //:: if prop.default_value:
         private final static ${prop.java_type.public_type} ${prop.default_name} = ${prop.default_value};
     //:: #endif
diff --git a/java_gen/templates/of_factories.java b/java_gen/templates/of_factories.java
index f8c9a80..f9ec015 100644
--- a/java_gen/templates/of_factories.java
+++ b/java_gen/templates/of_factories.java
@@ -26,7 +26,6 @@
 //:: # under the EPL.
 //::
 //:: import itertools
-//:: import of_g
 //:: include('_copyright.java')
 
 //:: include('_autogen.java')
@@ -43,13 +42,13 @@
         switch(version) {
             //:: for v in versions:
             case ${v.constant_version}:
-                return org.projectfloodlight.openflow.protocol.ver${v.of_version}.OFFactoryVer${v.of_version}.INSTANCE;
+                return org.projectfloodlight.openflow.protocol.ver${v.dotless_version}.OFFactoryVer${v.dotless_version}.INSTANCE;
             //:: #endfor
             default:
                 throw new IllegalArgumentException("Unknown version: "+version);
             }
     }
-    
+
     private static class GenericReader implements OFMessageReader<OFMessage> {
         public OFMessage readFrom(ChannelBuffer bb) throws OFParseError {
             short wireVersion = U8.f(bb.getByte(0));
@@ -57,7 +56,7 @@
             switch (wireVersion) {
             //:: for v in versions:
             case ${v.int_version}:
-                factory = org.projectfloodlight.openflow.protocol.ver${v.of_version}.OFFactoryVer${v.of_version}.INSTANCE;
+                factory = org.projectfloodlight.openflow.protocol.ver${v.dotless_version}.OFFactoryVer${v.dotless_version}.INSTANCE;
                 break;
             //:: #endfor
             default:
diff --git a/java_gen/templates/of_factory_class.java b/java_gen/templates/of_factory_class.java
index eef1e04..deb6e53 100644
--- a/java_gen/templates/of_factory_class.java
+++ b/java_gen/templates/of_factory_class.java
@@ -26,7 +26,6 @@
 //:: # under the EPL.
 //::
 //:: import itertools
-//:: import of_g
 //:: import re
 //:: include('_copyright.java')
 
@@ -47,7 +46,7 @@
 
     //:: for name, clazz in factory.interface.sub_factories.items():
     public ${clazz} ${name}() {
-        return ${clazz}Ver${factory.version.of_version}.INSTANCE;
+        return ${clazz}Ver${factory.version.dotless_version}.INSTANCE;
     }
     //:: #endfor
 
diff --git a/java_gen/templates/of_factory_interface.java b/java_gen/templates/of_factory_interface.java
index 9a77aa7..3694530 100644
--- a/java_gen/templates/of_factory_interface.java
+++ b/java_gen/templates/of_factory_interface.java
@@ -26,7 +26,6 @@
 //:: # under the EPL.
 //::
 //:: import itertools
-//:: import of_g
 //:: import re
 //:: include('_copyright.java')
 
diff --git a/java_gen/templates/of_interface.java b/java_gen/templates/of_interface.java
index 98d7b9e..a515ad1 100644
--- a/java_gen/templates/of_interface.java
+++ b/java_gen/templates/of_interface.java
@@ -27,7 +27,6 @@
 //::
 //:: import itertools
 //:: import re
-//:: import of_g
 //:: include('_copyright.java')
 
 //:: include('_autogen.java')
diff --git a/java_gen/templates/of_virtual_class.java b/java_gen/templates/of_virtual_class.java
index 55ccc5e..2c31c75 100644
--- a/java_gen/templates/of_virtual_class.java
+++ b/java_gen/templates/of_virtual_class.java
@@ -28,7 +28,6 @@
 //:: from loxi_ir import *
 //:: import os
 //:: import itertools
-//:: import of_g
 //:: include('_copyright.java')
 
 //:: include('_autogen.java')
@@ -87,7 +86,7 @@
 //::           if not model.generate_class(sub):
                // skip ${sub.name} - excluded from generation
 //::           else:
-//::           m = sub.get_member(prop.name)
+//::           m = sub.member_by_name(prop.name)
 //::           if not m.is_fixed_value:
 //::                  raise Exception("subtype %s of %s does not have fixed value for discriminator %s" %
 //::                           (sub.name, msg.name, prop.name))
diff --git a/java_gen/templates/unit_test.java b/java_gen/templates/unit_test.java
index cd85a74..ad2c3b8 100644
--- a/java_gen/templates/unit_test.java
+++ b/java_gen/templates/unit_test.java
@@ -27,7 +27,6 @@
 //::
 //:: from loxi_ir import *
 //:: import itertools
-//:: import of_g
 //:: import java_gen.java_model as java_model
 //:: include('_copyright.java')
 
@@ -40,6 +39,9 @@
 import org.junit.Test;
 import static org.junit.Assert.*;
 
+import org.hamcrest.CoreMatchers;
+
+
 public class ${test.name} {
     //:: factory = java_model.model.factory_of(test.interface)
     //:: var_type = msg.interface.name
@@ -67,7 +69,7 @@
         byte[] written = new byte[bb.readableBytes()];
         bb.readBytes(written);
 
-        assertArrayEquals(${msg.constant_name}_SERIALIZED, written);
+        assertThat(written, CoreMatchers.equalTo(${msg.constant_name}_SERIALIZED));
     }
 
     @Test
@@ -102,7 +104,7 @@
        byte[] written = new byte[bb.readableBytes()];
        bb.readBytes(written);
 
-       assertArrayEquals(${msg.constant_name}_SERIALIZED, written);
+       assertThat(written, CoreMatchers.equalTo(${msg.constant_name}_SERIALIZED));
    }
 
 }
diff --git a/lang_c.py b/lang_c.py
index 9cfc2fa..762f82c 100644
--- a/lang_c.py
+++ b/lang_c.py
@@ -33,6 +33,8 @@
 """
 
 import os
+import c_gen.of_g_legacy as of_g
+import c_gen.build_of_g as build_of_g
 import c_gen.c_code_gen as c_code_gen
 import c_gen.c_test_gen as c_test_gen
 import c_gen.c_dump_gen as c_dump_gen
@@ -40,6 +42,7 @@
 import c_gen.c_validator_gen as c_validator_gen
 import c_gen.util
 import loxi_utils.loxi_utils as loxi_utils
+import template_utils
 
 def static(out, name):
     c_gen.util.render_template(out, os.path.basename(name))
@@ -112,7 +115,61 @@
     'locitest/Makefile': static,
 }
 
-def generate():
+################################################################
+#
+# Configuration related
+#
+################################################################
+
+def config_check(str, dictionary = of_g.code_gen_config):
+    """
+    Return config value if in dictionary; else return False.
+    @param str The lookup index
+    @param dictionary The dict to check; use code_gen_config if None
+    """
+
+    if str in dictionary:
+        return dictionary[str]
+
+    return False
+
+def config_sanity_check():
+    """
+    Check the configuration for basic consistency
+
+    @fixme Needs update for generic language support
+    """
+
+    rv = True
+    # For now, only "error" supported for get returns
+    if config_check("copy_semantics") != "read":
+        debug("Only 'read' is supported for copy_semantics");
+        rv = False
+    if config_check("get_returns") != "error":
+        debug("Only 'error' is supported for get-accessor return types\m");
+        rv = False
+    if not config_check("use_fn_ptrs") and not config_check("gen_unified_fns"):
+        debug("Must have gen_fn_ptrs and/or gen_unified_fns set in config")
+        rv = False
+    if config_check("use_obj_id"):
+        debug("use_obj_id is set but not yet supported (change \
+config_sanity_check if it is)")
+        rv = False
+    if config_check("gen_unified_macros") and config_check("gen_unified_fns") \
+            and config_check("gen_unified_macro_lower"):
+        debug("Conflict: Cannot generate unified functions and lower case \
+unified macros")
+        rv = False
+
+    return rv
+
+def generate(install_dir):
+    build_of_g.initialize_versions()
+    build_of_g.build_ordered_classes()
+    build_of_g.populate_type_maps()
+    build_of_g.analyze_input()
+    build_of_g.unify_input()
+    build_of_g.order_and_assign_object_ids()
     for (name, fn) in targets.items():
-        with loxi_utils.open_output(name) as outfile:
+        with template_utils.open_output(install_dir, name) as outfile:
             fn(outfile, os.path.basename(name))
diff --git a/lang_java.py b/lang_java.py
index 3891076..f103330 100644
--- a/lang_java.py
+++ b/lang_java.py
@@ -31,5 +31,5 @@
 
 import java_gen.codegen as java_codegen
 
-def generate():
-    java_codegen.gen_all_java()
+def generate(install_dir):
+    java_codegen.gen_all_java(install_dir)
diff --git a/lang_python.py b/lang_python.py
index 9c9d13e..f40cecc 100644
--- a/lang_python.py
+++ b/lang_python.py
@@ -62,10 +62,13 @@
 """
 
 import os
+from loxi_globals import OFVersions
+import loxi_globals
 import loxi_utils.loxi_utils as loxi_utils
 import py_gen
 import py_gen.util
 import py_gen.codegen
+import template_utils
 
 versions = {
     1: "of10",
@@ -100,10 +103,10 @@
     targets['%s/%s/__init__.py' % (prefix, subdir)] = make_gen('init', version)
     for module in modules[version]:
         filename = '%s/%s/%s.py' % (prefix, subdir, module)
-        targets[filename] = make_gen(module, version)
+        targets[filename] = make_gen(module, OFVersions.from_wire(version))
 
-def generate():
+def generate(install_dir):
     py_gen.codegen.init()
     for (name, fn) in targets.items():
-        with loxi_utils.open_output(name) as outfile:
+        with template_utils.open_output(install_dir, name) as outfile:
             fn(outfile, os.path.basename(name))
diff --git a/lang_wireshark.py b/lang_wireshark.py
index 3e862d2..525b241 100644
--- a/lang_wireshark.py
+++ b/lang_wireshark.py
@@ -38,5 +38,5 @@
 
 import wireshark_gen
 
-def generate():
-    wireshark_gen.generate()
+def generate(install_dir):
+    wireshark_gen.generate(install_dir)
diff --git a/loxi_front_end/frontend.py b/loxi_front_end/frontend.py
index f4dece5..a84508a 100644
--- a/loxi_front_end/frontend.py
+++ b/loxi_front_end/frontend.py
@@ -28,9 +28,8 @@
 from generic_utils import find
 from collections import namedtuple
 import copy
-import of_g
-import loxi_front_end.type_maps as type_maps
-from loxi_ir import *
+import loxi_globals
+import loxi_front_end.frontend_ir as ir
 
 class InputError(Exception):
     pass
@@ -46,23 +45,23 @@
 
 def create_member(m_ast, ctx):
     if m_ast[0] == 'pad':
-        return OFPadMember(length=m_ast[1])
+        return ir.OFPadMember(length=m_ast[1])
     elif m_ast[0] == 'type':
-        return OFTypeMember(name=m_ast[2], oftype=get_type(m_ast[1], ctx), value=m_ast[3])
+        return ir.OFTypeMember(name=m_ast[2], oftype=get_type(m_ast[1], ctx), value=m_ast[3])
     elif m_ast[0] == 'data':
         if m_ast[2] == 'length' or m_ast[2] == 'len': # Should be moved to parser
-            return OFLengthMember(name=m_ast[2], oftype=get_type(m_ast[1], ctx))
+            return ir.OFLengthMember(name=m_ast[2], oftype=get_type(m_ast[1], ctx))
         elif m_ast[2] == 'actions_len':
             # HACK only usage so far
-            return OFFieldLengthMember(name=m_ast[2], oftype=get_type(m_ast[1], ctx), field_name='actions')
+            return ir.OFFieldLengthMember(name=m_ast[2], oftype=get_type(m_ast[1], ctx), field_name='actions')
         else:
-            return OFDataMember(name=m_ast[2], oftype=get_type(m_ast[1], ctx))
+            return ir.OFDataMember(name=m_ast[2], oftype=get_type(m_ast[1], ctx))
     elif m_ast[0] == 'discriminator':
-        return OFDiscriminatorMember(name=m_ast[2], oftype=get_type(m_ast[1], ctx))
+        return ir.OFDiscriminatorMember(name=m_ast[2], oftype=get_type(m_ast[1], ctx))
     else:
         raise InputError("Dont know how to create member: %s" % m_ast[0])
 
-def create_ofinput(ast):
+def create_ofinput(filename, ast):
 
     """
     Create an OFInput from an AST
@@ -72,7 +71,7 @@
     @returns An OFInput object
     """
     ctx = FrontendCtx(set())
-    ofinput = OFInput(wire_versions=set(), classes=[], enums=[])
+    ofinput = ir.OFInput(filename, wire_versions=set(), classes=[], enums=[])
 
     for decl_ast in ast:
         if decl_ast[0] == 'struct':
@@ -84,11 +83,11 @@
             superclass = decl_ast[3]
             members = [create_member(m_ast, ctx) for m_ast in decl_ast[4]]
 
-            discriminators = [ m for m in members if isinstance(m, OFDiscriminatorMember) ]
+            discriminators = [ m for m in members if isinstance(m, ir.OFDiscriminatorMember) ]
             if len(discriminators) > 1:
                 raise InputError("%s: Cannot support more than one discriminator by class - got %s" %
                         (decl_ast[1], repr(discriminators)))
-            ofclass = OFClass(name=decl_ast[1], members=members, superclass=superclass,
+            ofclass = ir.OFClass(name=decl_ast[1], members=members, superclass=superclass,
                     virtual = len(discriminators) > 0,
                     params = { param: value for param, value in decl_ast[2] })
             ofinput.classes.append(ofclass)
@@ -97,16 +96,16 @@
             # 1: name
             # 2: potentially list of [param_name, param_value]
             # 3: list of [constant_name, constant_value]+
-            enum = OFEnum(name=decl_ast[1],
-                    entries=[OFEnumEntry(name=x[0], value=x[2], params={param:value for param, value in x[1] }) for x in decl_ast[3]],
+            enum = ir.OFEnum(name=decl_ast[1],
+                    entries=[ir.OFEnumEntry(name=x[0], value=x[2], params={param:value for param, value in x[1] }) for x in decl_ast[3]],
                     params = { param: value for param, value in decl_ast[2] }
                     )
             ofinput.enums.append(enum)
         elif decl_ast[0] == 'metadata':
             if decl_ast[1] == 'version':
                 if decl_ast[2] == 'any':
-                    ofinput.wire_versions.update(of_g.wire_ver_map.keys())
-                elif int(decl_ast[2]) in of_g.supported_wire_protos:
+                    ofinput.wire_versions.update(v.wire_version for v in loxi_globals.OFVersions.all_supported)
+                elif int(decl_ast[2]) in loxi_globals.OFVersions.wire_version_map:
                     ofinput.wire_versions.add(int(decl_ast[2]))
                 else:
                     raise InputError("Unrecognized wire protocol version %r" % decl_ast[2])
@@ -115,8 +114,4 @@
     if not ofinput.wire_versions:
         raise InputError("Missing #version metadata")
 
-    for used_enum in ctx.used_enums:
-        if not find(lambda e: e.name == used_enum, ofinput.enums):
-            raise Exception("Undeclared enum used in OFInput: {}".format(used_enum))
-
     return ofinput
diff --git a/loxi_ir.py b/loxi_front_end/frontend_ir.py
similarity index 74%
rename from loxi_ir.py
rename to loxi_front_end/frontend_ir.py
index a70f041..a927f94 100644
--- a/loxi_ir.py
+++ b/loxi_front_end/frontend_ir.py
@@ -28,11 +28,9 @@
 from generic_utils import find
 from collections import namedtuple
 
-# This module is intended to be imported like this: from loxi_ir import *
-# All public names are prefixed with 'OF'.
+# This module is represents the frontend IR.
 __all__ = [
     'OFInput',
-    'OFProtocol',
     'OFClass',
     'OFDataMember',
     'OFTypeMember',
@@ -51,23 +49,7 @@
 @param classes List of OFClass objects in the same order as in the file
 @param enums List of Enum objects in the same order as in the file
 """
-OFInput = namedtuple('OFInput', ['wire_versions', 'classes', 'enums'])
-
-"""
-One version of the OpenFlow protocol
-
-Combination of multiple OFInput objects.
-
-@param wire_version
-@param classes List of OFClass objects
-@param enums List of Enum objects
-"""
-class OFProtocol(namedtuple('OFProtocol', ['wire_version', 'classes', 'enums'])):
-    def class_by_name(self, name):
-        return find(lambda ofclass: ofclass.name == name, self.classes)
-
-    def enum_by_name(self, name):
-        return find(lambda enum: enum.name == name, self.enums)
+OFInput = namedtuple('OFInput', ['filename', 'wire_versions', 'classes', 'enums'])
 
 """
 An OpenFlow class
@@ -82,13 +64,7 @@
 @param members List of *Member objects
 @param params optional dictionary of parameters
 """
-class OFClass(namedtuple('OFClass', ['name', 'superclass', 'members', 'virtual', 'params'])):
-    def member_by_name(self, name):
-        return find(lambda m: hasattr(m, "name") and m.name == name, self.members)
-
-    @property
-    def discriminator(self):
-        return find(lambda m: type(m) == OFDiscriminatorMember, self.members)
+OFClass = namedtuple('OFClass', ['name', 'superclass', 'members', 'virtual', 'params'])
 
 """
 Normal field
@@ -161,14 +137,5 @@
 @params dict of optional params. Currently defined:
        - wire_type: the low_level type of the enum values (uint8,...)
 """
-class OFEnum(namedtuple('OFEnum', ['name', 'entries', 'params'])):
-    @property
-    def values(self):
-        return [(e.name, e.value) for e in self.entries]
-
-    @property
-    def is_bitmask(self):
-        return "bitmask" in self.params and self.params['bitmask']
-
-
+OFEnum = namedtuple('OFEnum', ['name', 'entries', 'params'])
 OFEnumEntry = namedtuple('OFEnumEntry', ['name', 'value', 'params'])
diff --git a/loxi_globals.py b/loxi_globals.py
new file mode 100644
index 0000000..e8b21bd
--- /dev/null
+++ b/loxi_globals.py
@@ -0,0 +1,73 @@
+# Copyright 2013, Big Switch Networks, Inc.
+#
+# LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
+# the following special exception:
+#
+# LOXI Exception
+#
+# As a special exception to the terms of the EPL, you may distribute libraries
+# generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
+# that copyright and licensing notices generated by LoxiGen are not altered or removed
+# from the LoxiGen Libraries and the notice provided below is (i) included in
+# the LoxiGen Libraries, if distributed in source code form and (ii) included in any
+# documentation for the LoxiGen Libraries, if distributed in binary form.
+#
+# Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
+#
+# You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
+# a copy of the EPL at:
+#
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# EPL for the specific language governing permissions and limitations
+# under the EPL.
+
+from loxi_ir import *
+from collections import OrderedDict
+
+#######################################################################
+### OFVersion registry
+#######################################################################
+
+class OFVersions:
+    VERSION_1_0 = OFVersion("1.0", 1)
+    VERSION_1_1 = OFVersion("1.1", 2)
+    VERSION_1_2 = OFVersion("1.2", 3)
+    VERSION_1_3 = OFVersion("1.3", 4)
+
+    all_supported = (
+        VERSION_1_0,
+        VERSION_1_1,
+        VERSION_1_2,
+        VERSION_1_3,
+    )
+
+    wire_version_map   = { v.wire_version : v for v in all_supported }
+    version_string_map = { v.version      : v for v in all_supported }
+
+    target_versions = []
+
+    @staticmethod
+    def from_wire(w):
+        return OFVersions.wire_version_map[w]
+
+    @staticmethod
+    def from_string(s):
+        return OFVersions.version_string_map[s]
+
+    @staticmethod
+    def from_strings(*strings):
+        return tuple(OFVersions.version_string_map[s] for s in strings)
+
+
+
+
+#######################################################################
+### OFVersions
+#######################################################################
+
+# map OFVersion -> OFProtocol
+ir = OrderedDict()
diff --git a/loxi_front_end/c_parse_utils.py b/loxi_ir/__init__.py
similarity index 66%
rename from loxi_front_end/c_parse_utils.py
rename to loxi_ir/__init__.py
index 5e8d471..b4f1c05 100644
--- a/loxi_front_end/c_parse_utils.py
+++ b/loxi_ir/__init__.py
@@ -25,27 +25,7 @@
 # EPL for the specific language governing permissions and limitations
 # under the EPL.
 
-##
-# @brief Utilities related to parsing C files
-#
-import of_g
-
-def type_dec_to_count_base(m_type):
-    """
-    Resolve a type declaration like uint8_t[4] to a count (4) and base_type
-    (uint8_t)
-
-    @param m_type The string type declaration to process
-    """
-    count = 1
-    chk_ar = m_type.split('[')
-    if len(chk_ar) > 1:
-        count_str = chk_ar[1].split(']')[0]
-        if count_str in of_g.ofp_constants:
-            count = of_g.ofp_constants[count_str]
-        else:
-            count = int(count_str)
-        base_type = chk_ar[0]
-    else:
-        base_type = m_type
-    return count, base_type
+# Import the model
+from ir import *
+from ir import build_protocol
+from unified import build_unified_ir
diff --git a/loxi_ir/ir.py b/loxi_ir/ir.py
new file mode 100644
index 0000000..e2c4a34
--- /dev/null
+++ b/loxi_ir/ir.py
@@ -0,0 +1,412 @@
+# Copyright 2013, Big Switch Networks, Inc.
+#
+# LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
+# the following special exception:
+#
+# LOXI Exception
+#
+# As a special exception to the terms of the EPL, you may distribute libraries
+# generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
+# that copyright and licensing notices generated by LoxiGen are not altered or removed
+# from the LoxiGen Libraries and the notice provided below is (i) included in
+# the LoxiGen Libraries, if distributed in source code form and (ii) included in any
+# documentation for the LoxiGen Libraries, if distributed in binary form.
+#
+# Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
+#
+# You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
+# a copy of the EPL at:
+#
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# EPL for the specific language governing permissions and limitations
+# under the EPL.
+
+from itertools import chain
+import logging
+import re
+import sys
+
+from collections import namedtuple, OrderedDict
+from generic_utils import find, memoize, OrderedSet
+from loxi_ir import ir_offset
+
+logger = logging.getLogger(__name__)
+
+# This module is intended to be imported like this: from loxi_ir import *
+# All public names are prefixed with 'OF'.
+__all__ = [
+    'OFVersion',
+    'OFProtocol',
+    'OFClass',
+    'OFUnifiedClass',
+    'OFDataMember',
+    'OFTypeMember',
+    'OFDiscriminatorMember',
+    'OFLengthMember',
+    'OFFieldLengthMember',
+    'OFPadMember',
+    'OFEnum',
+    'OFEnumEntry'
+]
+
+"""
+One version of the OpenFlow protocol
+@param version Official dotted version number (e.g., "1.0", "1.3")
+@param wire_version Integer wire version (1 for 1.0, 4 for 1.3)
+"""
+class OFVersion(namedtuple("OFVersion", ("version", "wire_version"))):
+    @property
+    @memoize
+    def constant(self):
+        """ return this version as an uppercase string suitable
+            for use as a c constant, e.g., "VERSION_1_3"
+        """
+        return self.constant_version(prefix="VERSION_")
+
+    @property
+    @memoize
+    def short_constant(self):
+        """ return this version as an uppercase string suitable
+            for use as a c constant, e.g., "OF_"
+        """
+        return self.constant_version(prefix="OF_")
+
+    def constant_version(self, prefix="VERSION_"):
+        return prefix + self.version.replace(".", "_")
+
+    def __repr__(self):
+        return "OFVersion(%s)" % self.version
+
+    def __str__(self):
+        return self.version
+
+    def __cmp__(self, other):
+        return cmp(self.wire_version, other.wire_version)
+
+"""
+One version of the OpenFlow protocol
+
+Combination of multiple OFInput objects.
+
+@param wire_version
+@param classes List of OFClass objects
+@param enums List of Enum objects
+"""
+class OFProtocol(namedtuple('OFProtocol', ['version', 'classes', 'enums'])):
+    def __init__(self, version, classes, enums):
+        super(OFProtocol, self).__init__(self, version, classes, enums)
+        assert version is None or isinstance(version, OFVersion)
+
+    def class_by_name(self, name):
+        return find(lambda ofclass: ofclass.name == name, self.classes)
+
+    def enum_by_name(self, name):
+        return find(lambda enum: enum.name == name, self.enums)
+
+"""
+An OpenFlow class
+
+All compound objects like messages, actions, instructions, etc are
+uniformly represented by this class.
+
+The members are in the same order as on the wire.
+
+@param name
+@param superclass_name of this classes' super class
+@param members List of *Member objects
+@param params optional dictionary of parameters
+"""
+class OFClass(namedtuple('OFClass', ['name', 'superclass', 'members', 'virtual', 'params', 'is_fixed_length', 'base_length'])):
+    def __init__(self, *a, **kw):
+        super(OFClass, self).__init__(self, *a, **kw)
+        # Back reference will be added by assignment
+        self.protocol = None
+
+    def member_by_name(self, name):
+        return find(lambda m: hasattr(m, "name") and m.name == name, self.members)
+
+    @property
+    def discriminator(self):
+        return find(lambda m: type(m) == OFDiscriminatorMember, self.members)
+
+    def is_instanceof(self, super_class_name):
+        if self.name == super_class_name:
+            return True
+        elif self.superclass is None:
+            return False
+        else:
+            return self.superclass.is_instanceof(super_class_name)
+
+    def is_subclassof(self, super_class_name):
+        return self.name != super_class_name and self.is_instanceof(super_class_name)
+
+    @property
+    def is_message(self):
+        return self.is_instanceof("of_header")
+
+    @property
+    def is_oxm(self):
+        return self.is_instanceof("of_oxm")
+
+    @property
+    def is_action(self):
+        return self.is_instanceof("of_action")
+
+    @property
+    def is_action_id(self):
+        return self.is_instanceof("of_action_id")
+
+    @property
+    def is_instruction(self):
+        return self.is_instanceof("of_instruction")
+
+    def __hash__(self):
+        return hash((self.name, self.protocol.wire_version if self.protocol else None))
+
+    @property
+    def length(self):
+        if self.is_fixed_length:
+            return self.base_length
+        else:
+            raise Exception("Not a fixed length class: {}".format(self.name))
+
+""" one class unified across openflow versions. Keeps around a map version->versioned_class """
+class OFUnifiedClass(OFClass):
+    def __new__(cls, version_classes, *a, **kw):
+        return super(OFUnifiedClass, cls).__new__(cls, *a, **kw)
+
+    def __init__(self, version_classes, *a, **kw):
+        super(OFUnifiedClass, self).__init__(*a, **kw)
+        self.version_classes = version_classes
+
+    def class_by_version(self, version):
+        return self.version_classes[version]
+
+
+
+""" A mixin for member classes. Keeps around the back reference of_class (for assignment by
+    build_protocol, and additional methods shared across Members. """
+class MemberMixin(object):
+    def __init__(self, *a, **kw):
+        super(MemberMixin, self).__init__(*a, **kw)
+        # Back reference will be added by assignment in build_protocol below
+        self.of_class = None
+
+    @property
+    def length(self):
+        if self.is_fixed_length:
+            return self.base_length
+        else:
+            raise Exception("Not a fixed length member: {}.{} [{}]".format(
+                self.of_class.name,
+                self.name if hasattr("self", name) else "(unnnamed)",
+                type(self).__name__))
+
+"""
+Normal field
+
+@param name
+@param oftype C-like type string
+
+Example: packet_in.buffer_id
+"""
+class OFDataMember(namedtuple('OFDataMember', ['name', 'oftype', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
+    pass
+
+"""
+Field that declares that this is an abstract super-class and
+that the sub classes will be discriminated based on this field.
+E.g., 'type' is the discriminator member of the abstract superclass
+of_action.
+
+@param name
+"""
+class OFDiscriminatorMember (namedtuple('OFDiscriminatorMember', ['name', 'oftype', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
+    pass
+
+"""
+Field used to determine the type of an OpenFlow object
+
+@param name
+@param oftype C-like type string
+@param value Fixed type value
+
+Example: packet_in.type, flow_add._command
+"""
+class OFTypeMember (namedtuple('OFTypeMember', ['name', 'oftype', 'value', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
+    pass
+
+"""
+Field with the length of the containing object
+
+@param name
+@param oftype C-like type string
+
+Example: packet_in.length, action_output.len
+"""
+class OFLengthMember (namedtuple('OFLengthMember', ['name', 'oftype', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
+    pass
+
+"""
+Field with the length of another field in the containing object
+
+@param name
+@param oftype C-like type string
+@param field_name Peer field whose length this field contains
+
+Example: packet_out.actions_len (only usage)
+"""
+class OFFieldLengthMember (namedtuple('OFFieldLengthMember', ['name', 'oftype', 'field_name', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
+    pass
+
+"""
+Zero-filled padding
+
+@param length Length in bytes
+
+Example: packet_in.pad
+"""
+class OFPadMember (namedtuple('OFPadMember', ['pad_length', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
+    pass
+
+"""
+An OpenFlow enumeration
+
+All values are Python ints.
+
+@param name
+@param entries List of OFEnumEntry objects in input order
+@params dict of optional params. Currently defined:
+       - wire_type: the low_level type of the enum values (uint8,...)
+"""
+class OFEnum(namedtuple('OFEnum', ['name', 'entries', 'params'])):
+    def __init__(self, *a, **kw):
+        super(OFEnum, self).__init__(*a, **kw)
+        # Back reference will be added by assignment
+        self.protocol = None
+
+    @property
+    def values(self):
+        return [(e.name, e.value) for e in self.entries]
+
+    @property
+    def is_bitmask(self):
+        return "bitmask" in self.params and self.params['bitmask']
+
+    @property
+    def wire_type(self):
+        return self.params['wire_type'] if 'wire_type' in self.params else self.name
+
+class OFEnumEntry(namedtuple('OFEnumEntry', ['name', 'value', 'params'])):
+    def __init__(self, *a, **kw):
+        super(OFEnumEntry, self).__init__(*a, **kw)
+        # Back reference will be added by assignment
+        self.enum = None
+
+class RedefinedException(Exception):
+    pass
+
+class ClassNotFoundException(Exception):
+    pass
+
+class DependencyCycleException(Exception):
+    pass
+
+def build_protocol(version, ofinputs):
+    name_frontend_classes = OrderedDict()
+    name_frontend_enums = OrderedDict()
+
+    for ofinput in ofinputs:
+        for c in ofinput.classes:
+            name = c.name
+            if name in name_frontend_classes:
+                raise RedefinedException("Error parsing {}. Class {} redefined (already defined in {})"
+                        .format(ofinput.filename, name,
+                            name_frontend_classes[name][1].filename))
+            else:
+                name_frontend_classes[name] = (c, ofinput)
+        for e in ofinput.enums:
+            name = e.name
+            if name in name_frontend_enums:
+                raise RedefinedException("Error parsing {}. Enum {} redefined (already defined in {})"
+                        .format(ofinput.filename, name,
+                            name_frontend_enums[name][1].filename))
+            else:
+                name_frontend_enums[name] = (e, ofinput)
+
+    name_enums = {}
+    for fe, _ in name_frontend_enums.values():
+        entries = tuple(OFEnumEntry(name=e.name, value=e.value,
+                        params=e.params) for e in fe.entries)
+        enum = OFEnum(name=fe.name,
+                      entries=entries,
+                      params=fe.params)
+        for e in entries:
+            e.enum = enum
+        name_enums[enum.name] = enum
+
+    name_classes = OrderedDict()
+    build_touch_classes = OrderedSet()
+
+    def convert_member_properties(props):
+        return { name if name != "length" else "pad_length" : value for name, value in props.items() }
+
+    def build_member(of_class, fe_member, length_info):
+        ir_class = globals()[type(fe_member).__name__]
+        member = ir_class(offset = length_info.offset,
+                        base_length = length_info.base_length,
+                        is_fixed_length=length_info.is_fixed_length,
+                        **convert_member_properties(vars(fe_member)))
+        member.of_class = of_class
+        return member
+
+    def build_class(name):
+        if name in name_classes:
+            return name_classes[name]
+        if name in build_touch_classes:
+            raise DependencyCycleException( "Dependency cycle: {}"
+                    .format(" -> ".join(list(build_touch_classes) + [name])))
+        if not name in name_frontend_classes:
+            raise ClassNotFoundException("Class not found: {}".format(name))
+
+        build_touch_classes.add(name)
+
+        fe, _ = name_frontend_classes[name]
+
+        superclass = build_class(fe.superclass) if fe.superclass else None
+
+        # make sure members on which we depend are built first (for calc_length)
+        for m in fe.members:
+            if not hasattr(m, "oftype"):
+                continue
+            for m_name in re.sub(r'_t$', '', m.oftype), m.oftype:
+                logger.debug("Checking {}".format(m_name))
+                if m_name in name_frontend_classes:
+                    build_class(m_name)
+
+        base_length, is_fixed_length, member_lengths = \
+           ir_offset.calc_lengths(version, fe, name_classes, name_enums)
+
+        members = []
+        c = OFClass(name=fe.name, superclass=superclass,
+                members=members, virtual=fe.virtual, params=fe.params,
+                is_fixed_length=is_fixed_length, base_length=base_length)
+
+        members.extend( build_member(c, fe_member, member_lengths[fe_member])
+                  for fe_member in fe.members)
+
+        name_classes[name] = c
+        build_touch_classes.remove(name)
+        return c
+
+    for name in sorted(name_frontend_classes.keys()):
+        c = build_class(name)
+
+    protocol = OFProtocol(version=version, classes=tuple(name_classes.values()), enums=tuple(name_enums.values()))
+    for e in chain(protocol.classes, protocol.enums):
+        e.protocol = protocol
+    return protocol
diff --git a/loxi_ir/ir_offset.py b/loxi_ir/ir_offset.py
new file mode 100644
index 0000000..0f27f5e
--- /dev/null
+++ b/loxi_ir/ir_offset.py
@@ -0,0 +1,192 @@
+## List of mixed data types
+#
+# This is a list of data types which require special treatment
+# because the underlying datatype has changed between versions.
+# The main example is port which went from 16 to 32 bits.  We
+# define per-version accessors for these types and those are
+# used in place of the normal ones.
+#
+# The wire protocol number is used to identify versions.  For now,
+# the value is the name of the type to use for that version
+#
+# This is the map between the external type (like of_port_no_t)
+# which is used by customers of this code and the internal
+# datatypes (like uint16_t) that appear on the wire for a
+# particular version.
+#
+from collections import namedtuple
+import logging
+
+import loxi_front_end.frontend_ir as fe
+import loxi_ir.ir
+
+ofp_constants = dict(
+    OF_MAX_TABLE_NAME_LEN = 32,
+    OF_MAX_PORT_NAME_LEN  = 16,
+    OF_ETH_ALEN = 6,
+    OF_DESC_STR_LEN   = 256,
+    OF_SERIAL_NUM_LEN = 32
+)
+
+
+of_mixed_types = dict(
+    of_port_no_t = {
+        1: "uint16_t",
+        2: "uint32_t",
+        3: "uint32_t",
+        4: "uint32_t",
+        "short_name":"port_no"
+        },
+    of_port_desc_t = {
+        1: "of_port_desc_t",
+        2: "of_port_desc_t",
+        3: "of_port_desc_t",
+        4: "of_port_desc_t",
+        "short_name":"port_desc"
+        },
+    of_bsn_vport_t = {
+        1: "of_bsn_vport_t",
+        2: "of_bsn_vport_t",
+        3: "of_bsn_vport_t",
+        4: "of_bsn_vport_t",
+        "short_name":"bsn_vport"
+        },
+    of_fm_cmd_t = { # Flow mod command went from u16 to u8
+        1: "uint16_t",
+        2: "uint8_t",
+        3: "uint8_t",
+        4: "uint8_t",
+        "short_name":"fm_cmd"
+        },
+    of_wc_bmap_t = { # Wildcard bitmap
+        1: "uint32_t",
+        2: "uint32_t",
+        3: "uint64_t",
+        4: "uint64_t",
+        "short_name":"wc_bmap"
+        },
+    of_match_bmap_t = { # Match bitmap
+        1: "uint32_t",
+        2: "uint32_t",
+        3: "uint64_t",
+        4: "uint64_t",
+        "short_name":"match_bmap"
+        },
+    of_match_t = { # Match object
+        1: "of_match_v1_t",
+        2: "of_match_v2_t",
+        3: "of_match_v3_t",
+        4: "of_match_v3_t",  # Currently uses same match as 1.2 (v3).
+        "short_name":"match"
+        },
+)
+
+## basic lengths
+of_base_lengths = dict(
+    char     = (1, True),
+    uint8_t  = (1, True),
+    uint16_t = (2, True),
+    uint32_t = (4, True),
+    uint64_t = (8, True),
+    of_mac_addr_t = (6, True),
+    of_ipv4_t = (4, True),
+    of_ipv6_t = (16, True),
+    of_port_name_t = (ofp_constants["OF_MAX_PORT_NAME_LEN"], True),
+    of_table_name_t = (ofp_constants["OF_MAX_TABLE_NAME_LEN"], True),
+    of_desc_str_t = (ofp_constants["OF_DESC_STR_LEN"], True),
+    of_serial_num_t = (ofp_constants["OF_SERIAL_NUM_LEN"], True),
+    of_match_v1_t = (40, True),
+    of_match_v2_t = (88, True),
+    of_match_v3_t = (8, False),
+    of_octets_t = (0, False),
+    of_bitmap_128_t = (16, True),
+)
+
+def type_dec_to_count_base(m_type):
+    """
+    Resolve a type declaration like uint8_t[4] to a count (4) and base_type
+    (uint8_t)
+
+    @param m_type The string type declaration to process
+    """
+    count = 1
+    chk_ar = m_type.split('[')
+    if len(chk_ar) > 1:
+        count_str = chk_ar[1].split(']')[0]
+        if count_str in ofp_constants:
+            count = ofp_constants[count_str]
+        else:
+            count = int(count_str)
+        base_type = chk_ar[0]
+    else:
+        base_type = m_type
+    return count, base_type
+
+
+LengthInfo = namedtuple("LengthInfo", ("offset", "base_length", "is_fixed_length"))
+
+def calc_lengths(version, fe_class, existing_classes, existing_enums):
+    offset_fixed = True
+    offset = 0
+
+    member_infos = {}
+    for member in fe_class.members:
+        member_offset = offset if offset_fixed else None
+
+        if isinstance(member, fe.OFPadMember):
+            member_base_length = member.length
+            member_fixed_length = True
+        else:
+            m_type = member.oftype
+            name = member.name
+
+            member_base_length = 0
+            if m_type.find("list(") == 0:
+                member_fixed_length = False
+            elif m_type.find("struct") == 0:
+                raise Exception("Error: recursive struct found: {}, {}"
+                                    .format(fe_class.name, name))
+            elif m_type == "octets":
+                member_fixed_length = False
+            else:
+                member_base_length, member_fixed_length = member_length(version, fe_class, member, existing_classes, existing_enums)
+
+        if not member_fixed_length:
+            offset_fixed = False
+
+        member_infos[member] = LengthInfo(member_offset, member_base_length,
+                member_fixed_length)
+        offset += member_base_length
+
+    base_length = offset
+    fixed_length = offset_fixed if not fe_class.virtual else False
+    return (base_length, fixed_length, member_infos)
+
+def member_length(version, fe_class, fe_member, existing_classes, existing_enums):
+    """
+    return the length of an ir member.
+
+    @return tuple (base_length, length_fixed)
+    """
+    count, base_type = type_dec_to_count_base(fe_member.oftype)
+
+    len_update = 0
+    if base_type in of_mixed_types:
+        base_type = of_mixed_types[base_type][version.wire_version]
+
+    base_class = base_type[:-2]
+    if base_class in existing_classes:
+        member_ir_class = existing_classes[base_class]
+        bytes = member_ir_class.base_length
+        length_fixed = member_ir_class.is_fixed_length
+    else:
+        if base_type in existing_enums:
+            enum = existing_enums[base_type]
+            base_type = enum.wire_type
+
+        if base_type in of_base_lengths:
+            bytes, length_fixed = of_base_lengths[base_type]
+        else:
+            raise Exception("Unknown type for {}.{}: {}".format(fe_class.name, fe_member.name, base_type))
+
+    return (count * bytes), length_fixed
diff --git a/loxi_ir/unified.py b/loxi_ir/unified.py
new file mode 100644
index 0000000..41b86dc
--- /dev/null
+++ b/loxi_ir/unified.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python
+# Copyright 2013, Big Switch Networks, Inc.
+#
+# LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
+# the following special exception:
+#
+# LOXI Exception
+#
+# As a special exception to the terms of the EPL, you may distribute libraries
+# generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
+# that copyright and licensing notices generated by LoxiGen are not altered or removed
+# from the LoxiGen Libraries and the notice provided below is (i) included in
+# the LoxiGen Libraries, if distributed in source code form and (ii) included in any
+# documentation for the LoxiGen Libraries, if distributed in binary form.
+#
+# Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
+#
+# You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
+# a copy of the EPL at:
+#
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# EPL for the specific language governing permissions and limitations
+# under the EPL.
+
+import copy
+from collections import OrderedDict
+from itertools import chain
+import logging
+
+import ir
+
+def build_unified_ir(name_protocol_map):
+    class UnifiedClassSpec(object):
+        def __init__(self, name):
+            self.name = name
+            self.members = OrderedDict()
+            self.superclass_name = None
+            self.superclass_set = False
+            self.params = OrderedDict()
+            self.version_class = OrderedDict()
+            self.virtual = False
+            self.base_length = None
+            self.is_fixed_length = True
+
+        def add_class(self, version, v_class):
+            for v_member in v_class.members:
+                if hasattr(v_member, "name"):
+                    if not v_member.name in self.members:
+                        self.members[v_member.name] = v_member
+                    else:
+                        if not type(self.members[v_member.name]) == type(v_member):
+                            raise Exception("Error unifying ir class {} - adding version: {} - member_type {} <-> {}".format(
+                                    self.name, v_class.protocol.version, self.members[v_member.name], v_member))
+
+            if not self.superclass_set:
+                self.superclass_name = v_class.superclass.name if v_class.superclass else None
+            else:
+                if self.superclass_name != v_class.superclass_name:
+                    raise Exception("Error unifying ir class {} - adding version {} - superclass: param {} <-> {}".format(
+                            self.name, v_class.protocol.version, self.superclass_name, v_class.superclass_name))
+
+            for name, value in v_class.params.items():
+                if not name in self.params:
+                    self.params[name] = value
+                else:
+                    if self.params[name] != value:
+                        raise Exception("Error unifying ir class {} - adding version: {} - param {} <-> {}".format(
+                                self.name, v_class.protocol.version, self.params[name], value))
+
+            if v_class.virtual:
+                self.virtual = True
+
+            if not v_class.is_fixed_length:
+                self.is_fixed_length = False
+
+            if self.base_length is None:
+                self.base_length = v_class.base_length
+            elif self.base_length != v_class.base_length:
+                self.is_fixed_length = False
+                if self.base_length > v_class.base_length:
+                    self.base_length = v_class.base_length
+            self.version_class[version] = v_class
+
+    class UnifiedEnumSpec(object):
+        def __init__(self, name):
+            self.name = name
+            self.entries = {}
+            self.params = {}
+            self.version_enums = OrderedDict()
+
+        def add_enum(self, version, v_enum):
+            for e in v_enum.entries:
+                if not e.name in self.entries:
+                    self.entries[e.name] = ir.OFEnumEntry(e.name, e.value, copy.copy(e.params))
+                else:
+                    entry = self.entries[e.name]
+                    for name, value in e.params.items():
+                        if not name in entry.params:
+                            entry.params[name] = value
+                        elif entry.params[name] != value:
+                            raise Exception("Error unifying ir enum {} - adding version: param {} <-> {}".format(
+                                self.name, entry.params[name], value))
+            for name, value in v_enum.params.items():
+                if not name in self.params:
+                    self.params[name] = value
+                else:
+                    if self.params[name] != value:
+                        if name == "wire_type":
+                            self.params[name] = None
+                        else:
+                            raise Exception("Error unifying ir enum {} - adding version: {} param {} <-> {}".format(
+                                self.name, v_enum.protocol.version, self.params[name], value))
+
+            self.version_enums[version]=v_enum
+
+    u_name_classes = OrderedDict()
+    u_name_enums = OrderedDict()
+
+    for version, protocol in name_protocol_map.items():
+        assert isinstance(version, ir.OFVersion)
+        for v_class in protocol.classes:
+            name = v_class.name
+            if not name in u_name_classes:
+                u_name_classes[name] = UnifiedClassSpec(name)
+            spec = u_name_classes[name]
+            spec.add_class(version, v_class)
+
+        for v_enum in protocol.enums:
+            name = v_enum.name
+            if not name in u_name_enums:
+                u_name_enums[name] = UnifiedEnumSpec(name)
+            spec = u_name_enums[name]
+            spec.add_enum(version, v_enum)
+
+    unified_enums = tuple(ir.OFEnum(name=s.name, entries=tuple(s.entries.values()), params=s.params) for s in u_name_enums.values())
+    unified_classes = OrderedDict()
+    for name, spec in u_name_classes.items():
+        u = ir.OFUnifiedClass(
+                name = spec.name,
+                version_classes=spec.version_class,
+                superclass=None if not spec.superclass_name else unified_classes[spec.superclass_name],
+                members=spec.members.values(),
+                virtual=spec.virtual,
+                params=spec.params,
+                base_length=spec.base_length,
+                is_fixed_length=spec.is_fixed_length)
+        unified_classes[name] = u
+
+    unified = ir.OFProtocol(version=None, classes = tuple(unified_classes.values()), enums=unified_enums)
+    for e in chain(unified.classes, unified.enums):
+        e.protocol = unified
+    return unified
diff --git a/loxi_utils/loxi_utils.py b/loxi_utils/loxi_utils.py
index 059c363..d5937ac 100644
--- a/loxi_utils/loxi_utils.py
+++ b/loxi_utils/loxi_utils.py
@@ -33,41 +33,12 @@
 These may need to be sorted out into language specific functions
 """
 
+import re
 import sys
-import os
-import of_g
-import tenjin
+
+import loxi_globals
 from generic_utils import find, memoize
 
-def class_signature(members):
-    """
-    Generate a signature string for a class in canonical form
-
-    @param cls The class whose signature is to be generated
-    """
-    return ";".join([",".join([x["m_type"], x["name"], str(x["offset"])])
-                     for x in members])
-
-def type_dec_to_count_base(m_type):
-    """
-    Resolve a type declaration like uint8_t[4] to a count (4) and base_type
-    (uint8_t)
-
-    @param m_type The string type declaration to process
-    """
-    count = 1
-    chk_ar = m_type.split('[')
-    if len(chk_ar) > 1:
-        count_str = chk_ar[1].split(']')[0]
-        if count_str in of_g.ofp_constants:
-            count = of_g.ofp_constants[count_str]
-        else:
-            count = int(count_str)
-        base_type = chk_ar[0]
-    else:
-        base_type = m_type
-    return count, base_type
-
 ##
 # Class types:
 #
@@ -90,51 +61,30 @@
 #
 #
 
+class NoneClass(object):
+    def is_instanceof(self, x):
+        return False
+none_item = NoneClass()
+
+def _unified_by_name(cls):
+    c = loxi_globals.unified.class_by_name(cls)
+    return c if c is not None else none_item
+
+@memoize
 def class_is_message(cls):
     """
     Return True if cls is a message object based on info in unified
     """
-    return "xid" in of_g.unified[cls]["union"] and cls != "of_header"
-
-def class_is_tlv16(cls):
-    """
-    Return True if cls_name is an object which uses uint16 for type and length
-    """
-    if cls.find("of_action") == 0: # Includes of_action_id classes
-        return True
-    if cls.find("of_instruction") == 0:
-        return True
-    if cls.find("of_queue_prop") == 0:
-        return True
-    if cls.find("of_table_feature_prop") == 0:
-        return True
-    # *sigh*
-    if cls.find("of_meter_band_stats") == 0:  # NOT A TLV
+    if cls == "of_header":
         return False
-    if cls.find("of_meter_band") == 0:
-        return True
-    if cls.find("of_hello_elem") == 0:
-        return True
-    if cls == "of_match_v3":
-        return True
-    if cls == "of_match_v4":
-        return True
-    return False
-
-def class_is_u16_len(cls):
-    """
-    Return True if cls_name is an object which uses initial uint16 length
-    """
-    return cls in ["of_group_desc_stats_entry", "of_group_stats_entry",
-                   "of_flow_stats_entry", "of_bucket", "of_table_features"]
+    else:
+        return _unified_by_name(cls).is_instanceof("of_header")
 
 def class_is_oxm(cls):
     """
     Return True if cls_name is an OXM object
     """
-    if cls.find("of_oxm") == 0:
-        return True
-    return False
+    return _unified_by_name(cls).is_instanceof("of_oxm")
 
 def class_is_action(cls):
     """
@@ -145,17 +95,7 @@
     is used to identify a kind of action, it does not indicate the
     type of the object following.
     """
-    if cls.find("of_action_id") == 0:
-        return False
-    if cls.find("of_action") == 0:
-        return True
-
-    # For each vendor, check for vendor specific action
-    for exp in of_g.experimenter_name_to_id:
-        if cls.find("of_action" + exp) == 0:
-            return True
-
-    return False
+    return _unified_by_name(cls).is_instanceof("of_action")
 
 def class_is_action_id(cls):
     """
@@ -166,77 +106,44 @@
     is used to identify a kind of action, it does not indicate the
     type of the object following.
     """
-    if cls.find("of_action_id") == 0:
-        return True
-
-    # For each vendor, check for vendor specific action
-    for exp in of_g.experimenter_name_to_id:
-        if cls.find("of_action_id_" + exp) == 0:
-            return True
-
-    return False
+    return _unified_by_name(cls).is_instanceof("of_action_id")
 
 def class_is_instruction(cls):
     """
     Return True if cls_name is an instruction object
     """
-    if cls.find("of_instruction") == 0:
-        return True
-
-    # For each vendor, check for vendor specific action
-    for exp in of_g.experimenter_name_to_id:
-        if cls.find("of_instruction_" + exp) == 0:
-            return True
-
-    return False
+    return _unified_by_name(cls).is_instanceof("of_instruction")
 
 def class_is_meter_band(cls):
     """
     Return True if cls_name is an instruction object
     """
-    # meter_band_stats is not a member of meter_band class hierarchy
-    if cls.find("of_meter_band_stats") == 0:
-        return False
-    if cls.find("of_meter_band") == 0:
-        return True
-    return False
+    return _unified_by_name(cls).is_instanceof("of_meter_band")
 
 def class_is_hello_elem(cls):
     """
     Return True if cls_name is an instruction object
     """
-    if cls.find("of_hello_elem") == 0:
-        return True
-    return False
+    return _unified_by_name(cls).is_instanceof("of_hello_elem")
 
 def class_is_queue_prop(cls):
     """
     Return True if cls_name is a queue_prop object
     """
-    if cls.find("of_queue_prop") == 0:
-        return True
-
-    # For each vendor, check for vendor specific action
-    for exp in of_g.experimenter_name_to_id:
-        if cls.find("of_queue_prop_" + exp) == 0:
-            return True
-
-    return False
+    return _unified_by_name(cls).is_instanceof("of_queue_prop")
 
 def class_is_table_feature_prop(cls):
     """
     Return True if cls_name is a queue_prop object
     """
-    if cls.find("of_table_feature_prop") == 0:
-        return True
-    return False
+    return _unified_by_name(cls).is_instanceof("of_table_feature_prop")
 
 def class_is_stats_message(cls):
     """
     Return True if cls_name is a message object based on info in unified
     """
-
-    return "stats_type" in of_g.unified[cls]["union"]
+    u = _unified_by_name(cls)
+    return u.is_instanceof("of_stats_request") or u.ir_instanceof("of_stats_reply")
 
 def class_is_list(cls):
     """
@@ -249,311 +156,15 @@
     Return True if m_type is an OF object type
     """
     # Remove _t from the type id and see if key for unified class
-    if m_type[-2:] == "_t":
-        m_type = m_type[:-2]
-    return m_type in of_g.unified
-
-def list_to_entry_type(cls):
-    """
-    Return the entry type for a list
-    """
-    slen = len("of_list_")
-    return "of_" + cls[slen:]
-
-def type_to_short_name(m_type):
-    if m_type in of_g.of_base_types:
-        tname = of_g.of_base_types[m_type]["short_name"]
-    elif m_type in of_g.of_mixed_types:
-        tname = of_g.of_mixed_types[m_type]["short_name"]
-    else:
-        tname = "unknown"
-    return tname
-
-def type_to_name_type(cls, member_name):
-    """
-    Generate the root name of a member for accessor functions, etc
-    @param cls The class name
-    @param member_name The member name
-    """
-    members = of_g.unified[cls]["union"]
-    if not member_name in members:
-        debug("Error:  %s is not in class %s for acc_name defn" %
-              (member_name, cls))
-        os.exit()
-
-    mem = members[member_name]
-    m_type = mem["m_type"]
-    id = mem["memid"]
-    tname = type_to_short_name(m_type)
-
-    return "o%d_m%d_%s" % (of_g.unified[cls]["object_id"], id, tname)
-
-
-def member_to_index(m_name, members):
-    """
-    Given a member name, return the index in the members dict
-    @param m_name The name of the data member to search for
-    @param members The dict of members
-    @return Index if found, -1 not found
-
-    Note we could generate an index when processing the original input
-    """
-    count = 0
-    for d in members:
-        if d["name"] == m_name:
-            return count
-        count += 1
-    return -1
-
-def member_base_type(cls, m_name):
-    """
-    Map a member to its of_ type
-    @param cls The class name
-    @param m_name The name of the member being gotten
-    @return The of_ type of the member
-    """
-    rv = of_g.unified[cls]["union"][m_name]["m_type"]
-    if rv[-2:] == "_t":
-        return rv
-    return rv + "_t"
-
-def member_type_is_octets(cls, m_name):
-    return member_base_type(cls, m_name) == "of_octets_t"
-
-def member_returns_val(cls, m_name):
-    """
-    Should get accessor return a value rather than void
-    @param cls The class name
-    @param m_name The member name
-    @return True if of_g config and the specific member allow a
-    return value.  Otherwise False
-    """
-    m_type = of_g.unified[cls]["union"][m_name]["m_type"]
-    return (config_check("get_returns") =="value" and
-            m_type in of_g.of_scalar_types)
-
-def config_check(str, dictionary = of_g.code_gen_config):
-    """
-    Return config value if in dictionary; else return False.
-    @param str The lookup index
-    @param dictionary The dict to check; use code_gen_config if None
-    """
-
-    if str in dictionary:
-        return dictionary[str]
-
-    return False
-
-def h_file_to_define(name):
-    """
-    Convert a .h file name to the define used for the header
-    """
-    h_name = name[:-2].upper()
-    h_name = "_" + h_name + "_H_"
-    return h_name
-
-def type_to_cof_type(m_type):
-    if m_type in of_g.of_base_types:
-        if "cof_type" in of_g.of_base_types[m_type]:
-            return of_g.of_base_types[m_type]["cof_type"]
-    return m_type
-
-
-def member_is_scalar(cls, m_name):
-    return of_g.unified[cls]["union"][m_name]["m_type"] in of_g.of_scalar_types
-
-def type_is_scalar(m_type):
-    return m_type in of_g.of_scalar_types
-
-def skip_member_name(name):
-    return name.find("pad") == 0 or name in of_g.skip_members
-
-def enum_name(cls):
-    """
-    Return the name used for an enum identifier for the given class
-    @param cls The class name
-    """
-    return cls.upper()
-
-def class_in_version(cls, ver):
-    """
-    Return boolean indicating if cls is defined for wire version ver
-    """
-
-    return (cls, ver) in of_g.base_length
-
-def instance_to_class(instance, parent):
-    """
-    Return the name of the class for an instance of inheritance type parent
-    """
-    return parent + "_" + instance
-
-def sub_class_to_var_name(cls):
-    """
-    Given a subclass name like of_action_output, generate the
-    name of a variable like 'output'
-    @param cls The class name
-    """
-    pass
-
-def class_is_var_len(cls, version):
-    # Match is special case.  Only version 1.2 (wire version 3) is var
-    if cls == "of_match":
-        return version == 3
-
-    return not (cls, version) in of_g.is_fixed_length
-
-def base_type_to_length(base_type, version):
-    if base_type + "_t" in of_g.of_base_types:
-        inst_len = of_g.of_base_types[base_type + "_t"]["bytes"]
-    else:
-        inst_len = of_g.base_length[(base_type, version)]
-
-def version_to_name(version):
-    """
-    Convert an integer version to the C macro name
-    """
-    return "OF_" + of_g.version_names[version]
-
-##
-# Is class a flow modify of some sort?
-
-def cls_is_flow_mod(cls):
-    return cls in ["of_flow_mod", "of_flow_modify", "of_flow_add", "of_flow_delete",
-                   "of_flow_modify_strict", "of_flow_delete_strict"]
-
-
-def all_member_types_get(cls, version):
-    """
-    Get the members and list of types for members of a given class
-    @param cls The class name to process
-    @param version The version for the class
-    """
-    member_types = []
-
-    if not version in of_g.unified[cls]:
-        return ([], [])
-
-    if "use_version" in of_g.unified[cls][version]:
-        v = of_g.unified[cls][version]["use_version"]
-        members = of_g.unified[cls][v]["members"]
-    else:
-        members = of_g.unified[cls][version]["members"]
-    # Accumulate variables that are supported
-    for member in members:
-        m_type = member["m_type"]
-        m_name = member["name"]
-        if skip_member_name(m_name):
-            continue
-        if not m_type in member_types:
-            member_types.append(m_type)
-
-    return (members, member_types)
-
-def list_name_extract(list_type):
-    """
-    Return the base name for a list object of the given type
-    @param list_type The type of the list as appears in the input,
-    for example list(of_port_desc_t).
-    @return A pair, (list-name, base-type) where list-name is the
-    base name for the list, for example of_list_port_desc, and base-type
-    is the type of list elements like of_port_desc_t
-    """
-    base_type = list_type[5:-1]
-    list_name = base_type
-    if list_name.find("of_") == 0:
-        list_name = list_name[3:]
-    if list_name[-2:] == "_t":
-        list_name = list_name[:-2]
-    list_name = "of_list_" + list_name
-    return (list_name, base_type)
-
-def version_to_name(version):
-    """
-    Convert an integer version to the C macro name
-    """
-    return "OF_" + of_g.version_names[version]
-
-def gen_c_copy_license(out):
-    """
-    Generate the top comments for copyright and license
-    """
-    import c_gen.util
-    c_gen.util.render_template(out, '_copyright.c')
-
-def accessor_returns_error(a_type, m_type):
-    is_var_len = (not type_is_scalar(m_type)) and \
-        [x for x in of_g.of_version_range if class_is_var_len(m_type[:-2], x)] != []
-    if a_type == "set" and is_var_len:
-        return True
-    elif m_type == "of_match_t":
-        return True
-    else:
-        return False
-
-def render_template(out, name, path, context, prefix = None):
-    """
-    Render a template using tenjin.
-    out: a file-like object
-    name: name of the template
-    path: array of directories to search for the template
-    context: dictionary of variables to pass to the template
-    prefix: optional prefix to use for embedding (for other languages than python)
-    """
-    pp = [ tenjin.PrefixedLinePreprocessor(prefix=prefix) if prefix else tenjin.PrefixedLinePreprocessor() ] # support "::" syntax
-    template_globals = { "to_str": str, "escape": str } # disable HTML escaping
-    engine = TemplateEngine(path=path, pp=pp)
-    out.write(engine.render(name, context, template_globals))
-
-def render_static(out, name, path):
-    """
-    Write out a static template.
-    out: a file-like object
-    name: name of the template
-    path: array of directories to search for the template
-    """
-    # Reuse the tenjin logic for finding the template
-    template_filename = tenjin.FileSystemLoader().find(name, path)
-    if not template_filename:
-        raise ValueError("template %s not found" % name)
-    with open(template_filename) as infile:
-        out.write(infile.read())
+    return _unified_by_name(re.sub(r'_t$', '', m_type)) != none_item
 
 @memoize
 def lookup_ir_wiretype(oftype, version):
     """ if of is a reference to an enum in ir, resolve it to the wiretype
         declared in that enum. Else return oftype """
-    enums = of_g.ir[version].enums
+    enums = loxi_globals.ir[version].enums
     enum = find(lambda e: e.name == oftype, enums)
     if enum and 'wire_type' in enum.params:
         return enum.params['wire_type']
     else:
         return oftype
-
-class TemplateEngine(tenjin.Engine):
-    def include(self, template_name, **kwargs):
-        """
-        Tenjin has an issue with nested includes that use the same local variable
-        names, because it uses the same context dict for each level of nesting.
-        The fix is to copy the context.
-        """
-        frame = sys._getframe(1)
-        locals  = frame.f_locals
-        globals = frame.f_globals
-        context = locals["_context"].copy()
-        context.update(kwargs)
-        template = self.get_template(template_name, context, globals)
-        return template.render(context, globals, _buf=locals["_buf"])
-
-def open_output(name):
-    """
-    Open an output file for writing
-
-    'name' may include slashes. Subdirectories will be automatically created.
-    """
-    print "Writing %s" % name
-    path = os.path.join(of_g.options.install_dir, name)
-    dirpath = os.path.dirname(path)
-    if not os.path.exists(dirpath):
-        os.makedirs(dirpath)
-    return open(path, "w")
diff --git a/loxigen.py b/loxigen.py
index 7b78f63..700168a 100755
--- a/loxigen.py
+++ b/loxigen.py
@@ -69,248 +69,27 @@
 
 """
 
-import sys
-
+from collections import OrderedDict, defaultdict
+import copy
+import glob
+from optparse import OptionParser
+import os
 import re
 import string
-import os
-import glob
-import copy
-import collections
-import of_g
-import loxi_front_end.type_maps as type_maps
+import sys
+
+import cmdline
+from loxi_globals import OFVersions
+import loxi_globals
 import loxi_utils.loxi_utils as loxi_utils
-import loxi_front_end.c_parse_utils as c_parse_utils
-import loxi_front_end.identifiers as identifiers
 import pyparsing
 import loxi_front_end.parser as parser
-import loxi_front_end.translation as translation
 import loxi_front_end.frontend as frontend
-from loxi_ir import *
+import loxi_ir
 from generic_utils import *
 
 root_dir = os.path.dirname(os.path.realpath(__file__))
 
-# TODO:  Put these in a class so they get documented
-
-## Dict indexed by version giving all info related to version
-#
-# This is local; after processing, the information is stored in
-# of_g variables.
-versions = {}
-
-def config_sanity_check():
-    """
-    Check the configuration for basic consistency
-
-    @fixme Needs update for generic language support
-    """
-
-    rv = True
-    # For now, only "error" supported for get returns
-    if config_check("copy_semantics") != "read":
-        debug("Only 'read' is supported for copy_semantics");
-        rv = False
-    if config_check("get_returns") != "error":
-        debug("Only 'error' is supported for get-accessor return types\m");
-        rv = False
-    if not config_check("use_fn_ptrs") and not config_check("gen_unified_fns"):
-        debug("Must have gen_fn_ptrs and/or gen_unified_fns set in config")
-        rv = False
-    if config_check("use_obj_id"):
-        debug("use_obj_id is set but not yet supported (change \
-config_sanity_check if it is)")
-        rv = False
-    if config_check("gen_unified_macros") and config_check("gen_unified_fns") \
-            and config_check("gen_unified_macro_lower"):
-        debug("Conflict: Cannot generate unified functions and lower case \
-unified macros")
-        rv = False
-
-    return rv
-
-def add_class(wire_version, cls, members):
-    """
-    Process a class for the given version and update the unified
-    list of classes as needed.
-
-    @param wire_version The wire version for this class defn
-    @param cls The name of the class being added
-    @param members The list of members with offsets calculated
-    """
-    memid = 0
-
-    sig = loxi_utils.class_signature(members)
-    if cls in of_g.unified:
-        uc = of_g.unified[cls]
-        if wire_version in uc:
-            debug("Error adding %s to unified. Wire ver %d exists" %
-                  (cls, wire_version))
-            sys.exit(1)
-        uc[wire_version] = {}
-        # Check for a matching signature
-        for wver in uc:
-            if type(wver) != type(0): continue
-            if wver == wire_version: continue
-            if not "use_version" in uc[wver]:
-                if sig == loxi_utils.class_signature(uc[wver]["members"]):
-                    log("Matched %s, ver %d to ver %d" %
-                          (cls, wire_version, wver))
-                    # have a match with existing version
-                    uc[wire_version]["use_version"] = wver
-                    # What else to do?
-                    return
-    else:  # Haven't seen this entry before
-        log("Adding %s to unified list, ver %d" % (cls, wire_version))
-        of_g.unified[cls] = dict(union={})
-        uc = of_g.unified[cls]
-
-    # At this point, need to add members for this version
-    uc[wire_version] = dict(members = members)
-
-    # Per member processing:
-    #  Add to union list (I'm sure there's a better way)
-    #  Check if it's a list
-    union = uc["union"]
-    if not cls in of_g.ordered_members:
-        of_g.ordered_members[cls] = []
-    for member in members:
-        m_name = member["name"]
-        m_type = member["m_type"]
-        if m_name.find("pad") == 0:
-            continue
-        if m_name in union:
-            if not m_type == union[m_name]["m_type"]:
-                debug("ERROR:   CLASS: %s. VERSION %d. MEMBER: %s. TYPE: %s" %
-                      (cls, wire_version, m_name, m_type))
-                debug("    Type conflict adding member to unified set.")
-                debug("    Current union[%s]:" % m_name)
-                debug(union[m_name])
-                sys.exit(1)
-        else:
-            union[m_name] = dict(m_type=m_type, memid=memid)
-            memid += 1
-        if not m_name in of_g.ordered_members[cls]:
-            of_g.ordered_members[cls].append(m_name)
-
-def update_offset(cls, wire_version, name, offset, m_type):
-    """
-    Update (and return) the offset based on type.
-    @param cls The parent class
-    @param wire_version The wire version being processed
-    @param name The name of the data member
-    @param offset The current offset
-    @param m_type The type declaration being processed
-    @returns A pair (next_offset, len_update)  next_offset is the new offset
-    of the next object or -1 if this is a var-length object.  len_update
-    is the increment that should be added to the length.  Note that (for
-    of_match_v3) it is variable length, but it adds 8 bytes to the fixed
-    length of the object
-    If offset is already -1, do not update
-    Otherwise map to base type and count and update (if possible)
-    """
-    if offset < 0:    # Don't update offset once set to -1
-        return offset, 0
-
-    count, base_type = c_parse_utils.type_dec_to_count_base(m_type)
-
-    len_update = 0
-    if base_type in of_g.of_mixed_types:
-        base_type = of_g.of_mixed_types[base_type][wire_version]
-
-    base_class = base_type[:-2]
-    if (base_class, wire_version) in of_g.is_fixed_length:
-        bytes = of_g.base_length[(base_class, wire_version)]
-    else:
-        if base_type == "of_match_v3_t":
-            # This is a special case: it has non-zero min length
-            # but is variable length
-            bytes = -1
-            len_update = 8
-        elif base_type in of_g.of_base_types:
-            bytes = of_g.of_base_types[base_type]["bytes"]
-        else:
-            print "UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type)
-            log("UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type))
-            bytes = -1
-
-    # If bytes
-    if bytes > 0:
-        len_update = count * bytes
-
-    if bytes == -1:
-        return -1, len_update
-
-    return offset + (count * bytes), len_update
-
-def calculate_offsets_and_lengths(ordered_classes, classes, wire_version):
-    """
-    Generate the offsets for fixed offset class members
-    Also calculate the class_sizes when possible.
-
-    @param classes The classes to process
-    @param wire_version The wire version for this set of classes
-
-    Updates global variables
-    """
-
-    lists = set()
-
-    # Generate offsets
-    for cls in ordered_classes:
-        fixed_offset = 0 # The last "good" offset seen
-        offset = 0
-        last_offset = 0
-        last_name = "-"
-        for member in classes[cls]:
-            m_type = member["m_type"]
-            name = member["name"]
-            if last_offset == -1:
-                if name == "pad":
-                    log("Skipping pad for special offset for %s" % cls)
-                else:
-                    log("SPECIAL OFS: Member %s (prev %s), class %s ver %d" %
-                          (name, last_name, cls, wire_version))
-                    if (((cls, name) in of_g.special_offsets) and
-                        (of_g.special_offsets[(cls, name)] != last_name)):
-                        debug("ERROR: special offset prev name changed")
-                        debug("  cls %s. name %s. version %d. was %s. now %s" %
-                              cls, name, wire_version,
-                              of_g.special_offsets[(cls, name)], last_name)
-                        sys.exit(1)
-                    of_g.special_offsets[(cls, name)] = last_name
-
-            member["offset"] = offset
-            if m_type.find("list(") == 0:
-                (list_name, base_type) = loxi_utils.list_name_extract(m_type)
-                lists.add(list_name)
-                member["m_type"] = list_name + "_t"
-                offset = -1
-            elif m_type.find("struct") == 0:
-                debug("ERROR found struct: %s.%s " % (cls, name))
-                sys.exit(1)
-            elif m_type == "octets":
-                log("offset gen skipping octets: %s.%s " % (cls, name))
-                offset = -1
-            else:
-                offset, len_update = update_offset(cls, wire_version, name,
-                                                  offset, m_type)
-                if offset != -1:
-                    fixed_offset = offset
-                else:
-                    fixed_offset += len_update
-                    log("offset is -1 for %s.%s version %d " %
-                        (cls, name, wire_version))
-            last_offset = offset
-            last_name = name
-        of_g.base_length[(cls, wire_version)] = fixed_offset
-        if (offset != -1):
-            of_g.is_fixed_length.add((cls, wire_version))
-    for list_type in lists:
-        classes[list_type] = []
-        of_g.ordered_classes[wire_version].append(list_type)
-        of_g.base_length[(list_type, wire_version)] = 0
-
 def process_input_file(filename):
     """
     Process an input file
@@ -332,78 +111,13 @@
 
     # Create the OFInput from the AST
     try:
-        ofinput = frontend.create_ofinput(ast)
+        ofinput = frontend.create_ofinput(os.path.basename(filename), ast)
     except frontend.InputError as e:
         print "Error in %s: %s" % (os.path.basename(filename), str(e))
         sys.exit(1)
 
     return ofinput
 
-def order_and_assign_object_ids():
-    """
-    Order all classes and assign object ids to all classes.
-
-    This is done to promote a reasonable order of the objects, putting
-    messages first followed by non-messages.  No assumptions should be
-    made about the order, nor about contiguous numbering.  However, the
-    numbers should all be reasonably small allowing arrays indexed by
-    these enum values to be defined.
-    """
-
-    # Generate separate message and non-message ordered lists
-    for cls in of_g.unified:
-        if loxi_utils.class_is_message(cls):
-            of_g.ordered_messages.append(cls)
-        elif loxi_utils.class_is_list(cls):
-            of_g.ordered_list_objects.append(cls)
-        else:
-            of_g.ordered_non_messages.append(cls)
-
-    of_g.ordered_messages.sort()
-    of_g.ordered_pseudo_objects.sort()
-    of_g.ordered_non_messages.sort()
-    of_g.ordered_list_objects.sort()
-    of_g.standard_class_order.extend(of_g.ordered_messages)
-    of_g.standard_class_order.extend(of_g.ordered_non_messages)
-    of_g.standard_class_order.extend(of_g.ordered_list_objects)
-
-    # This includes pseudo classes for which most code is not generated
-    of_g.all_class_order.extend(of_g.ordered_messages)
-    of_g.all_class_order.extend(of_g.ordered_non_messages)
-    of_g.all_class_order.extend(of_g.ordered_list_objects)
-    of_g.all_class_order.extend(of_g.ordered_pseudo_objects)
-
-    # Assign object IDs
-    for cls in of_g.ordered_messages:
-        of_g.unified[cls]["object_id"] = of_g.object_id
-        of_g.object_id += 1
-    for cls in of_g.ordered_non_messages:
-        of_g.unified[cls]["object_id"] = of_g.object_id
-        of_g.object_id += 1
-    for cls in of_g.ordered_list_objects:
-        of_g.unified[cls]["object_id"] = of_g.object_id
-        of_g.object_id += 1
-    for cls in of_g.ordered_pseudo_objects:
-        of_g.unified[cls] = {}
-        of_g.unified[cls]["object_id"] = of_g.object_id
-        of_g.object_id += 1
-
-
-def initialize_versions():
-    """
-    Create an empty datastructure for each target version.
-    """
-
-    for wire_version in of_g.target_version_list:
-        version_name = of_g.of_version_wire2name[wire_version]
-        of_g.wire_ver_map[wire_version] = version_name
-        versions[version_name] = dict(
-            version_name = version_name,
-            wire_version = wire_version,
-            classes = {})
-        of_g.ordered_classes[wire_version] = []
-
-
 def read_input():
     """
     Read in from files given on command line and update global state
@@ -411,255 +125,62 @@
     @fixme Should select versions to support from command line
     """
 
-    ofinputs_by_version = collections.defaultdict(lambda: [])
+    ofinputs_by_version = defaultdict(lambda: [])
     filenames = sorted(glob.glob("%s/openflow_input/*" % root_dir))
 
     # Ignore emacs backup files
     filenames = [x for x in filenames if not x.endswith('~')]
 
+    # Read input files
+    all_ofinputs = []
     for filename in filenames:
         log("Processing struct file: " + filename)
         ofinput = process_input_file(filename)
 
-        # Populate global state
         for wire_version in ofinput.wire_versions:
             ofinputs_by_version[wire_version].append(ofinput)
-            version_name = of_g.of_version_wire2name[wire_version]
+    return ofinputs_by_version
 
-            for ofclass in ofinput.classes:
-                of_g.ordered_classes[wire_version].append(ofclass.name)
-                legacy_members = []
-                pad_count = 0
-                for m in ofclass.members:
-                    if type(m) == OFPadMember:
-                        m_name = 'pad%d' % pad_count
-                        if m_name == 'pad0': m_name = 'pad'
-                        legacy_members.append(dict(m_type='uint8_t[%d]' % m.length,
-                                                   name=m_name))
-                        pad_count += 1
-                    else:
-                        # HACK the C backend does not yet support of_oxm_t
-                        if m.oftype == 'of_oxm_t':
-                            m_type = 'of_octets_t'
-                        else:
-                            enum = find(lambda e: e.name == m.oftype, ofinput.enums)
-                            if enum and "wire_type" in enum.params:
-                                m_type = enum.params["wire_type"]
-                            else:
-                                m_type = m.oftype
-                        legacy_members.append(dict(m_type=m_type, name=m.name))
-                versions[version_name]['classes'][ofclass.name] = legacy_members
+def build_ir(ofinputs_by_version):
+    classes = []
+    enums = []
+    for wire_version, ofinputs in ofinputs_by_version.items():
+        version = OFVersions.from_wire(wire_version)
+        ofprotocol = loxi_ir.build_protocol(version, ofinputs)
+        loxi_globals.ir[version] = ofprotocol
 
-            for enum in ofinput.enums:
-                for entry in enum.entries:
-                    identifiers.add_identifier(
-                        translation.loxi_name(entry.name),
-                        entry.name, enum.name, entry.value, wire_version,
-                        of_g.identifiers, of_g.identifiers_by_group)
+    loxi_globals.unified = loxi_ir.build_unified_ir(loxi_globals.ir)
 
-        for wire_version, ofinputs in ofinputs_by_version.items():
-            ofprotocol = OFProtocol(wire_version=wire_version, classes=[], enums=[])
-            for ofinput in ofinputs:
-                ofprotocol.classes.extend(ofinput.classes)
-                ofprotocol.enums.extend(ofinput.enums)
-            ofprotocol.classes.sort(key=lambda ofclass: ofclass.name)
-            of_g.ir[wire_version] = ofprotocol
-
-def populate_type_maps():
-    """
-    Use the type members in the IR to fill out the legacy type_maps.
-    """
-
-    def split_inherited_cls(cls):
-        if cls == 'of_meter_band_stats': # HACK not a subtype of of_meter_band
-            return None, None
-        for parent in sorted(type_maps.inheritance_data.keys(), reverse=True):
-            if cls.startswith(parent):
-                return (parent, cls[len(parent)+1:])
-        return None, None
-
-    def find_experimenter(parent, cls):
-        for experimenter in sorted(of_g.experimenter_name_to_id.keys(), reverse=True):
-            prefix = parent + '_' + experimenter
-            if cls.startswith(prefix) and cls != prefix:
-                return experimenter
-        return None
-
-    def find_type_value(ofclass, m_name):
-        for m in ofclass.members:
-            if isinstance(m, OFTypeMember) and m.name == m_name:
-                return m.value
-        raise KeyError("ver=%d, cls=%s, m_name=%s" % (wire_version, cls, m_name))
-
-    # Most inheritance classes: actions, instructions, etc
-    for wire_version, protocol in of_g.ir.items():
-        for ofclass in protocol.classes:
-            cls = ofclass.name
-            parent, subcls = split_inherited_cls(cls)
-            if not (parent and subcls):
-                continue
-            if parent == 'of_oxm':
-                type_len = find_type_value(ofclass, 'type_len')
-                oxm_class = (type_len >> 16) & 0xffff
-                if oxm_class != 0x8000:
-                    # Do not include experimenter OXMs in the main table
-                    val = type_maps.invalid_type
-                else:
-                    val = (type_len >> 8) & 0xff
-            else:
-                val = find_type_value(ofclass, 'type')
-            type_maps.inheritance_data[parent][wire_version][subcls] = val
-
-            # Extensions (only actions for now)
-            experimenter = find_experimenter(parent, cls)
-            if parent == 'of_action' and experimenter:
-                val = find_type_value(ofclass, 'subtype')
-                type_maps.extension_action_subtype[wire_version][experimenter][cls] = val
-                if wire_version >= of_g.VERSION_1_3:
-                    cls2 = parent + "_id" + cls[len(parent):]
-                    type_maps.extension_action_id_subtype[wire_version][experimenter][cls2] = val
-
-    # Messages
-    for wire_version, protocol in of_g.ir.items():
-        for ofclass in protocol.classes:
-            cls = ofclass.name
-            # HACK (though this is what loxi_utils.class_is_message() does)
-            if not [x for x in ofclass.members if isinstance(x, OFDataMember) and x.name == 'xid']:
-                continue
-            if type_maps.class_is_virtual(cls):
-                continue
-            subcls = cls[3:]
-            val = find_type_value(ofclass, 'type')
-            if not val in type_maps.message_types[wire_version].values():
-                type_maps.message_types[wire_version][subcls] = val
-
-            # Extensions
-            experimenter = find_experimenter('of', cls)
-            if experimenter:
-                val = find_type_value(ofclass, 'subtype')
-                type_maps.extension_message_subtype[wire_version][experimenter][cls] = val
-
-    type_maps.generate_maps()
-
-def analyze_input():
-    """
-    Add information computed from the input, including offsets and
-    lengths of struct members and the set of list and action_id types.
-    """
-
-    # Generate header classes for inheritance parents
-    for wire_version, ordered_classes in of_g.ordered_classes.items():
-        classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
-        for cls in ordered_classes:
-            if cls in type_maps.inheritance_map:
-                new_cls = cls + '_header'
-                of_g.ordered_classes[wire_version].append(new_cls)
-                classes[new_cls] = classes[cls]
-
-    # Generate action_id classes for OF 1.3
-    for wire_version, ordered_classes in of_g.ordered_classes.items():
-        if not wire_version in [of_g.VERSION_1_3]:
-            continue
-        classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
-        for cls in ordered_classes:
-            if not loxi_utils.class_is_action(cls):
-                continue
-            action = cls[10:]
-            if action == '' or action == 'header':
-                continue
-            name = "of_action_id_" + action
-            members = classes["of_action"][:]
-            of_g.ordered_classes[wire_version].append(name)
-            if type_maps.action_id_is_extension(name, wire_version):
-                # Copy the base action classes thru subtype
-                members = classes["of_action_" + action][:4]
-            classes[name] = members
-
-    # @fixme If we support extended actions in OF 1.3, need to add IDs
-    # for them here
-
-    for wire_version in of_g.wire_ver_map.keys():
-        version_name = of_g.of_version_wire2name[wire_version]
-        calculate_offsets_and_lengths(
-            of_g.ordered_classes[wire_version],
-            versions[version_name]['classes'],
-            wire_version)
-
-def unify_input():
-    """
-    Create Unified View of Objects
-    """
-
-    global versions
-
-    # Add classes to unified in wire-format order so that it is easier
-    # to generate things later
-    keys = versions.keys()
-    keys.sort(reverse=True)
-    for version in keys:
-        wire_version = versions[version]["wire_version"]
-        classes = versions[version]["classes"]
-        for cls in of_g.ordered_classes[wire_version]:
-            add_class(wire_version, cls, classes[cls])
-
-
-def log_all_class_info():
-    """
-    Log the results of processing the input
-
-    Debug function
-    """
-
-    for cls in of_g.unified:
-        for v in of_g.unified[cls]:
-            if type(v) == type(0):
-                log("cls: %s. ver: %d. base len %d. %s" %
-                    (str(cls), v, of_g.base_length[(cls, v)],
-                     loxi_utils.class_is_var_len(cls,v) and "not fixed"
-                     or "fixed"))
-                if "use_version" in of_g.unified[cls][v]:
-                    log("cls %s: v %d mapped to %d" % (str(cls), v,
-                           of_g.unified[cls][v]["use_version"]))
-                if "members" in of_g.unified[cls][v]:
-                    for member in of_g.unified[cls][v]["members"]:
-                        log("   %-20s: type %-20s. offset %3d" %
-                            (member["name"], member["m_type"],
-                             member["offset"]))
-
-def generate_all_files():
-    """
-    Create the files for the language target
-    """
-    lang_module.generate()
+################################################################
+#
+# Debug
+#
+################################################################
 
 if __name__ == '__main__':
-    of_g.loxigen_log_file = open("loxigen.log", "w")
-    of_g.loxigen_dbg_file = sys.stdout
-
-    of_g.process_commandline()
+    (options, args, target_versions) = cmdline.process_commandline()
     # @fixme Use command line params to select log
 
-    if not config_sanity_check():
+    logging.basicConfig(level = logging.INFO if not options.verbose else logging.DEBUG)
+
+    # Import the language file
+    lang_file = "lang_%s" % options.lang
+    lang_module = __import__(lang_file)
+
+    if hasattr(lang_module, "config_sanity_check") and not lang_module.config_sanity_check():
         debug("Config sanity check failed\n")
         sys.exit(1)
 
-    # Import the language file
-    lang_file = "lang_%s" % of_g.options.lang
-    lang_module = __import__(lang_file)
-
     # If list files, just list auto-gen files to stdout and exit
-    if of_g.options.list_files:
+    if options.list_files:
         for name in lang_module.targets:
-            print of_g.options.install_dir + '/' + name
+            print options.install_dir + '/' + name
         sys.exit(0)
 
-    log("\nGenerating files for target language %s\n" % of_g.options.lang)
+    log("\nGenerating files for target language %s\n" % options.lang)
 
-    initialize_versions()
-    read_input()
-    populate_type_maps()
-    analyze_input()
-    unify_input()
-    order_and_assign_object_ids()
-    log_all_class_info()
-    generate_all_files()
+    loxi_globals.OFVersions.target_versions = target_versions
+    inputs = read_input()
+    build_ir(inputs)
+    #log_all_class_info()
+    lang_module.generate(options.install_dir)
diff --git a/openflow_input/bsn b/openflow_input/bsn
index 0aa674b..dfbf981 100644
--- a/openflow_input/bsn
+++ b/openflow_input/bsn
@@ -46,3 +46,26 @@
     pad(4);
 };
 
+struct of_bsn_stats_request : of_experimenter_stats_request {
+    uint8_t version;
+    uint8_t type == 18;
+    uint16_t length;
+    uint32_t xid;
+    uint16_t stats_type == 0xffff;
+    enum ofp_stats_request_flags flags;
+    pad(4);
+    uint32_t experimenter == 0x5c16c7;
+    uint32_t subtype == ?;
+};
+
+struct of_bsn_stats_reply : of_experimenter_stats_reply {
+    uint8_t version;
+    uint8_t type == 19;
+    uint16_t length;
+    uint32_t xid;
+    uint16_t stats_type == 0xffff;
+    enum ofp_stats_reply_flags flags;
+    pad(4);
+    uint32_t experimenter == 0x5c16c7;
+    uint32_t subtype == ?;
+};
diff --git a/openflow_input/bsn_lacp b/openflow_input/bsn_lacp
index 8237641..d2e540a 100644
--- a/openflow_input/bsn_lacp
+++ b/openflow_input/bsn_lacp
@@ -88,3 +88,45 @@
     uint16_t partner_port_num;
     uint16_t partner_key;
 };
+
+struct of_bsn_lacp_stats_request : of_bsn_stats_request {
+    uint8_t version;
+    uint8_t type == 18;
+    uint16_t length;
+    uint32_t xid;
+    uint16_t stats_type == 0xffff;
+    enum ofp_stats_request_flags flags;
+    pad(4);
+    uint32_t experimenter == 0x5c16c7;
+    uint32_t subtype == 1;
+};
+
+struct of_bsn_lacp_stats_entry {
+    of_port_no_t port_no;
+    uint16_t actor_sys_priority;
+    of_mac_addr_t actor_sys_mac;
+    uint16_t actor_port_priority;
+    uint16_t actor_port_num;
+    uint16_t actor_key;
+    uint8_t convergence_status;
+    pad(1);
+    uint16_t partner_sys_priority;
+    of_mac_addr_t partner_sys_mac;
+    uint16_t partner_port_priority;
+    uint16_t partner_port_num;
+    uint16_t partner_key;
+    pad(2);
+};
+
+struct of_bsn_lacp_stats_reply : of_bsn_stats_reply {
+    uint8_t version;
+    uint8_t type == 19;
+    uint16_t length;
+    uint32_t xid;
+    uint16_t stats_type == 0xffff;
+    enum ofp_stats_reply_flags flags;
+    pad(4);
+    uint32_t experimenter == 0x5c16c7;
+    uint32_t subtype == 1;
+    list(of_bsn_lacp_stats_entry_t) entries;
+};
diff --git a/openflow_input/standard-1.1 b/openflow_input/standard-1.1
index 9b72e8c..906519c 100644
--- a/openflow_input/standard-1.1
+++ b/openflow_input/standard-1.1
@@ -984,8 +984,44 @@
     uint8_t type == 15;
     uint16_t length;
     uint32_t xid;
-    uint16_t command;
-    uint8_t group_type;
+    enum ofp_group_mod_command command == ?;
+    enum ofp_group_type group_type;
+    pad(1);
+    uint32_t group_id;
+    list(of_bucket_t) buckets;
+};
+
+struct of_group_add : of_group_mod {
+    uint8_t version;
+    uint8_t type == 15;
+    uint16_t length;
+    uint32_t xid;
+    enum ofp_group_mod_command command == 0;
+    enum ofp_group_type group_type;
+    pad(1);
+    uint32_t group_id;
+    list(of_bucket_t) buckets;
+};
+
+struct of_group_modify : of_group_mod {
+    uint8_t version;
+    uint8_t type == 15;
+    uint16_t length;
+    uint32_t xid;
+    enum ofp_group_mod_command command == 1;
+    enum ofp_group_type group_type;
+    pad(1);
+    uint32_t group_id;
+    list(of_bucket_t) buckets;
+};
+
+struct of_group_delete : of_group_mod {
+    uint8_t version;
+    uint8_t type == 15;
+    uint16_t length;
+    uint32_t xid;
+    enum ofp_group_mod_command command == 2;
+    enum ofp_group_type group_type;
     pad(1);
     uint32_t group_id;
     list(of_bucket_t) buckets;
diff --git a/openflow_input/standard-1.2 b/openflow_input/standard-1.2
index e9d915a..90447f5 100644
--- a/openflow_input/standard-1.2
+++ b/openflow_input/standard-1.2
@@ -898,8 +898,44 @@
     uint8_t type == 15;
     uint16_t length;
     uint32_t xid;
-    uint16_t command;
-    uint8_t group_type;
+    enum ofp_group_mod_command command == ?;
+    enum ofp_group_type group_type;
+    pad(1);
+    uint32_t group_id;
+    list(of_bucket_t) buckets;
+};
+
+struct of_group_add : of_group_mod {
+    uint8_t version;
+    uint8_t type == 15;
+    uint16_t length;
+    uint32_t xid;
+    enum ofp_group_mod_command command == 0;
+    enum ofp_group_type group_type;
+    pad(1);
+    uint32_t group_id;
+    list(of_bucket_t) buckets;
+};
+
+struct of_group_modify : of_group_mod {
+    uint8_t version;
+    uint8_t type == 15;
+    uint16_t length;
+    uint32_t xid;
+    enum ofp_group_mod_command command == 1;
+    enum ofp_group_type group_type;
+    pad(1);
+    uint32_t group_id;
+    list(of_bucket_t) buckets;
+};
+
+struct of_group_delete : of_group_mod {
+    uint8_t version;
+    uint8_t type == 15;
+    uint16_t length;
+    uint32_t xid;
+    enum ofp_group_mod_command command == 2;
+    enum ofp_group_type group_type;
     pad(1);
     uint32_t group_id;
     list(of_bucket_t) buckets;
@@ -1078,7 +1114,7 @@
     of_octets_t data;
 };
 
-struct of_experimenter_error_msg {
+struct of_experimenter_error_msg : of_error_msg {
     uint8_t version;
     uint8_t type == 1;
     uint16_t length;
diff --git a/openflow_input/standard-1.3 b/openflow_input/standard-1.3
index db7ff0d..4d0ca7e 100644
--- a/openflow_input/standard-1.3
+++ b/openflow_input/standard-1.3
@@ -1058,8 +1058,44 @@
     uint8_t type == 15;
     uint16_t length;
     uint32_t xid;
-    uint16_t command;
-    uint8_t group_type;
+    enum ofp_group_mod_command command == ?;
+    enum ofp_group_type group_type;
+    pad(1);
+    uint32_t group_id;
+    list(of_bucket_t) buckets;
+};
+
+struct of_group_add : of_group_mod {
+    uint8_t version;
+    uint8_t type == 15;
+    uint16_t length;
+    uint32_t xid;
+    enum ofp_group_mod_command command == 0;
+    enum ofp_group_type group_type;
+    pad(1);
+    uint32_t group_id;
+    list(of_bucket_t) buckets;
+};
+
+struct of_group_modify : of_group_mod {
+    uint8_t version;
+    uint8_t type == 15;
+    uint16_t length;
+    uint32_t xid;
+    enum ofp_group_mod_command command == 1;
+    enum ofp_group_type group_type;
+    pad(1);
+    uint32_t group_id;
+    list(of_bucket_t) buckets;
+};
+
+struct of_group_delete : of_group_mod {
+    uint8_t version;
+    uint8_t type == 15;
+    uint16_t length;
+    uint32_t xid;
+    enum ofp_group_mod_command command == 2;
+    enum ofp_group_type group_type;
     pad(1);
     uint32_t group_id;
     list(of_bucket_t) buckets;
@@ -1302,7 +1338,7 @@
     of_octets_t data;
 };
 
-struct of_experimenter_error_msg {
+struct of_experimenter_error_msg : of_error_msg {
     uint8_t version;
     uint8_t type == 1;
     uint16_t length;
@@ -1527,6 +1563,30 @@
     list(of_table_stats_entry_t) entries;
 };
 
+struct of_experimenter_stats_request : of_stats_request {
+    uint8_t version;
+    uint8_t type == 18;
+    uint16_t length;
+    uint32_t xid;
+    uint16_t stats_type == 0xffff;
+    enum ofp_stats_request_flags flags;
+    pad(4);
+    uint32_t experimenter == ?;
+    uint32_t subtype;
+};
+
+struct of_experimenter_stats_reply : of_stats_reply {
+    uint8_t version;
+    uint8_t type == 19;
+    uint16_t length;
+    uint32_t xid;
+    uint16_t stats_type == 0xffff;
+    enum ofp_stats_reply_flags flags;
+    pad(4);
+    uint32_t experimenter == ?;
+    uint32_t subtype;
+};
+
 // FIXME: These are padded to 8 byte align beyond the length indicated
 
 struct of_table_feature_prop {
diff --git a/py_gen/codegen.py b/py_gen/codegen.py
index 70053f7..7cd068f 100644
--- a/py_gen/codegen.py
+++ b/py_gen/codegen.py
@@ -26,9 +26,9 @@
 # under the EPL.
 
 from collections import namedtuple
+import loxi_globals
 import struct
-import of_g
-import loxi_front_end.type_maps as type_maps
+import template_utils
 import loxi_utils.loxi_utils as utils
 import util
 import oftype
@@ -57,7 +57,7 @@
 # HACK the oftype member attribute is replaced with an OFType instance
 def build_ofclasses(version):
     ofclasses = []
-    for ofclass in of_g.ir[version].classes:
+    for ofclass in loxi_globals.ir[version].classes:
         cls = ofclass.name
         if ofclass.virtual:
             continue
@@ -81,7 +81,10 @@
                     members.append(OFTypeMember(
                         name=m.name,
                         oftype=m.oftype,
-                        value=version))
+                        value=version.wire_version,
+                        base_length=m.base_length,
+                        is_fixed_length=m.is_fixed_length,
+                        offset=m.offset))
                     type_members.append(members[-1])
                 else:
                     members.append(m)
@@ -91,8 +94,8 @@
                       pyname=generate_pyname(cls),
                       members=members,
                       type_members=type_members,
-                      min_length=of_g.base_length[(cls, version)],
-                      is_fixed_length=(cls, version) in of_g.is_fixed_length,
+                      min_length=ofclass.base_length,
+                      is_fixed_length=ofclass.is_fixed_length,
                       has_internal_alignment=cls == 'of_action_set_field',
                       has_external_alignment=cls == 'of_match_v3'))
     return ofclasses
@@ -122,7 +125,7 @@
 
 def generate_const(out, name, version):
     util.render_template(out, 'const.py', version=version,
-                         enums=of_g.ir[version].enums)
+                         enums=loxi_globals.ir[version].enums)
 
 def generate_instruction(out, name, version):
     ofclasses = [x for x in ofclasses_by_version[version]
@@ -146,5 +149,5 @@
     util.render_template(out, 'util.py', version=version)
 
 def init():
-    for version in of_g.supported_wire_protos:
+    for version in loxi_globals.OFVersions.target_versions:
         ofclasses_by_version[version] = build_ofclasses(version)
diff --git a/py_gen/oftype.py b/py_gen/oftype.py
index c378cea..605d5be 100644
--- a/py_gen/oftype.py
+++ b/py_gen/oftype.py
@@ -28,7 +28,6 @@
 from collections import namedtuple
 
 import loxi_utils.loxi_utils as loxi_utils
-import of_g
 
 OFTypeData = namedtuple("OFTypeData", ["init", "pack", "unpack"])
 
@@ -204,6 +203,7 @@
     'list(of_table_stats_entry_t)': 'common.table_stats_entry.unpack',
     'list(of_uint32_t)': 'common.uint32.unpack',
     'list(of_uint8_t)': 'common.uint8.unpack',
+    'list(of_bsn_lacp_stats_entry_t)': 'common.bsn_lacp_stats_entry.unpack',
 }
 
 for (cls, element_deserializer) in fixed_elem_len_lists.items():
diff --git a/py_gen/templates/_pretty_print.py b/py_gen/templates/_pretty_print.py
index 74e6d79..c8c7770 100644
--- a/py_gen/templates/_pretty_print.py
+++ b/py_gen/templates/_pretty_print.py
@@ -24,6 +24,7 @@
 :: # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 :: # EPL for the specific language governing permissions and limitations
 :: # under the EPL.
+:: from loxi_globals import OFVersions
 :: import loxi_utils.loxi_utils as loxi_utils
 ::
         q.text("${ofclass.pyname} {")
@@ -49,7 +50,7 @@
                 q.text(util.pretty_mac(self.${m.name}))
 :: elif m.oftype == 'of_ipv4_t':
                 q.text(util.pretty_ipv4(self.${m.name}))
-:: elif m.oftype == 'of_wc_bmap_t' and version in [1,2]:
+:: elif m.oftype == 'of_wc_bmap_t' and version in OFVersions.from_strings("1.0", "1.1"):
                 q.text(util.pretty_wildcards(self.${m.name}))
 :: elif m.oftype == 'of_port_no_t':
                 q.text(util.pretty_port(self.${m.name}))
diff --git a/py_gen/templates/action.py b/py_gen/templates/action.py
index fcfd9a5..92c4e26 100644
--- a/py_gen/templates/action.py
+++ b/py_gen/templates/action.py
@@ -26,7 +26,7 @@
 :: # under the EPL.
 ::
 :: import itertools
-:: import of_g
+:: from loxi_globals import OFVersions
 :: import py_gen.util as util
 :: include('_copyright.py')
 
@@ -37,7 +37,7 @@
 import util
 import loxi.generic_util
 import loxi
-:: if version >= 3:
+:: if version >= OFVersions.VERSION_1_2:
 import oxm # for unpack
 :: #endif
 
diff --git a/py_gen/templates/common.py b/py_gen/templates/common.py
index 1ab2f68..76ae631 100644
--- a/py_gen/templates/common.py
+++ b/py_gen/templates/common.py
@@ -26,23 +26,23 @@
 :: # under the EPL.
 ::
 :: include('_copyright.py')
-
+:: from loxi_globals import OFVersions
 :: include('_autogen.py')
 
 import sys
 import struct
 import action
-:: if version >= 2:
+:: if version >= OFVersions.VERSION_1_1:
 import instruction # for unpack_list
 :: #endif
-:: if version >= 4:
+:: if version >= OFVersions.VERSION_1_3:
 import meter_band # for unpack_list
 :: #endif
 import const
 import util
 import loxi.generic_util
 
-:: if version >= 3:
+:: if version >= OFVersions.VERSION_1_2:
 import oxm
 :: #endif
 
@@ -94,13 +94,13 @@
 
 :: #endfor
 
-:: if version == 1:
+:: if version == OFVersions.VERSION_1_0:
 match = match_v1
-:: elif version == 2:
+:: elif version == OFVersions.VERSION_1_1:
 match = match_v2
-:: elif version == 3:
+:: elif version == OFVersions.VERSION_1_2:
 match = match_v3
-:: elif version == 4:
+:: elif version == OFVersions.VERSION_1_3:
 :: # HACK
 match = match_v3
 :: #endif
diff --git a/py_gen/templates/const.py b/py_gen/templates/const.py
index 4567d56..d1c00f9 100644
--- a/py_gen/templates/const.py
+++ b/py_gen/templates/const.py
@@ -24,6 +24,7 @@
 :: # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 :: # EPL for the specific language governing permissions and limitations
 :: # under the EPL.
+:: from loxi_globals import OFVersions
 ::
 :: blacklisted_map_groups = ['macro_definitions']
 :: blacklisted_map_idents = ['OFPFW_NW_DST_BITS', 'OFPFW_NW_SRC_BITS',
@@ -34,12 +35,12 @@
 
 :: include('_autogen.py')
 
-OFP_VERSION = ${version}
+OFP_VERSION = ${version.wire_version}
 
 :: for enum in sorted(enums, key=lambda enum: enum.name):
 # Identifiers from group ${enum.name}
 ::    for (ident, value) in enum.values:
-::        if version == 1 and ident.startswith('OFPP_'):
+::        if version == OFVersions.VERSION_1_0 and ident.startswith('OFPP_'):
 ::        # HACK loxi converts these to 32-bit
 ${ident} = ${"%#x" % (value & 0xffff)}
 ::        else:
@@ -52,7 +53,7 @@
 ::        for (ident, value) in enum.values:
 ::            if ident in blacklisted_map_idents:
 ::                pass
-::            elif version == 1 and ident.startswith('OFPP_'):
+::            elif version == OFVersions.VERSION_1_0 and ident.startswith('OFPP_'):
 ::                # HACK loxi converts these to 32-bit
     ${"%#x" % (value & 0xffff)}: ${repr(ident)},
 ::        else:
diff --git a/py_gen/templates/instruction.py b/py_gen/templates/instruction.py
index 88b0f89..978e38b 100644
--- a/py_gen/templates/instruction.py
+++ b/py_gen/templates/instruction.py
@@ -26,7 +26,6 @@
 :: # under the EPL.
 ::
 :: import itertools
-:: import of_g
 :: import py_gen.util as util
 :: include('_copyright.py')
 
diff --git a/py_gen/templates/message.py b/py_gen/templates/message.py
index c8e31c9..39e9722 100644
--- a/py_gen/templates/message.py
+++ b/py_gen/templates/message.py
@@ -26,7 +26,8 @@
 :: # under the EPL.
 ::
 :: import itertools
-:: import of_g
+:: from loxi_globals import OFVersions
+:: import loxi_globals
 :: import py_gen.util as util
 :: import py_gen.oftype
 :: include('_copyright.py')
@@ -38,10 +39,10 @@
 import const
 import common
 import action # for unpack_list
-:: if version >= 2:
+:: if version >= OFVersions.VERSION_1_1:
 import instruction # for unpack_list
 :: #endif
-:: if version >= 4:
+:: if version >= OFVersions.VERSION_1_3:
 import meter_band # for unpack_list
 :: #endif
 import util
@@ -132,9 +133,9 @@
         raise loxi.ProtocolError("unexpected error type %u" % err_type)
 
 def parse_flow_mod(buf):
-:: if version == 1:
+:: if version == OFVersions.VERSION_1_0:
 :: offset = 57
-:: elif version >= 2:
+:: elif version >= OFVersions.VERSION_1_1:
 :: offset = 25
 :: #endif
     if len(buf) < ${offset} + 1:
@@ -146,6 +147,18 @@
     else:
         raise loxi.ProtocolError("unexpected flow mod cmd %u" % cmd)
 
+:: if version >= OFVersions.VERSION_1_0:
+def parse_group_mod(buf):
+:: offset = 8
+    if len(buf) < ${offset} + 2:
+        raise loxi.ProtocolError("message too short")
+    cmd, = struct.unpack_from("!H", buf, ${offset})
+    if cmd in flow_mod_parsers:
+        return group_mod_parsers[cmd](buf)
+    else:
+        raise loxi.ProtocolError("unexpected group mod cmd %u" % cmd)
+:: #endif
+
 def parse_stats_reply(buf):
     if len(buf) < 8 + 2:
         raise loxi.ProtocolError("message too short")
@@ -164,6 +177,30 @@
     else:
         raise loxi.ProtocolError("unexpected stats type %u" % stats_type)
 
+def parse_experimenter_stats_request(buf):
+    if len(buf) < 24:
+        raise loxi.ProtocolError("experimenter stats request message too short")
+
+    experimenter, exp_type = struct.unpack_from("!LL", buf, 16)
+
+    if experimenter in experimenter_stats_request_parsers and \
+            exp_type in experimenter_stats_request_parsers[experimenter]:
+        return experimenter_stats_request_parsers[experimenter][exp_type](buf)
+    else:
+        raise loxi.ProtocolError("unexpected stats request experimenter %#x exp_type %#x" % (experimenter, exp_type))
+
+def parse_experimenter_stats_reply(buf):
+    if len(buf) < 24:
+        raise loxi.ProtocolError("experimenter stats reply message too short")
+
+    experimenter, exp_type = struct.unpack_from("!LL", buf, 16)
+
+    if experimenter in experimenter_stats_reply_parsers and \
+            exp_type in experimenter_stats_reply_parsers[experimenter]:
+        return experimenter_stats_reply_parsers[experimenter][exp_type](buf)
+    else:
+        raise loxi.ProtocolError("unexpected stats reply experimenter %#x exp_type %#x" % (experimenter, exp_type))
+
 def parse_experimenter(buf):
     if len(buf) < 16:
         raise loxi.ProtocolError("experimenter message too short")
@@ -202,18 +239,18 @@
     const.OFPET_FLOW_MOD_FAILED : flow_mod_failed_error_msg.unpack,
     const.OFPET_PORT_MOD_FAILED : port_mod_failed_error_msg.unpack,
     const.OFPET_QUEUE_OP_FAILED : queue_op_failed_error_msg.unpack,
-:: if version >= of_g.VERSION_1_1:
+:: if version >= OFVersions.VERSION_1_1:
     const.OFPET_BAD_INSTRUCTION : bad_instruction_error_msg.unpack,
     const.OFPET_BAD_MATCH : bad_match_error_msg.unpack,
     const.OFPET_GROUP_MOD_FAILED : group_mod_failed_error_msg.unpack,
     const.OFPET_TABLE_MOD_FAILED : table_mod_failed_error_msg.unpack,
     const.OFPET_SWITCH_CONFIG_FAILED : switch_config_failed_error_msg.unpack,
 :: #endif
-:: if version >= of_g.VERSION_1_2:
+:: if version >= OFVersions.VERSION_1_2:
     const.OFPET_ROLE_REQUEST_FAILED : role_request_failed_error_msg.unpack,
     const.OFPET_EXPERIMENTER : experimenter_error_msg.unpack,
 :: #endif
-:: if version >= of_g.VERSION_1_3:
+:: if version >= OFVersions.VERSION_1_3:
     const.OFPET_METER_MOD_FAILED : meter_mod_failed_error_msg.unpack,
     const.OFPET_TABLE_FEATURES_FAILED : table_features_failed_error_msg.unpack,
 :: #endif
@@ -227,6 +264,14 @@
     const.OFPFC_DELETE_STRICT : flow_delete_strict.unpack,
 }
 
+:: if version >= OFVersions.VERSION_1_1:
+group_mod_parsers = {
+    const.OFPGC_ADD : group_add.unpack,
+    const.OFPGC_MODIFY : group_modify.unpack,
+    const.OFPGC_DELETE : group_delete.unpack,
+}
+:: #endif
+
 stats_reply_parsers = {
     const.OFPST_DESC : desc_stats_reply.unpack,
     const.OFPST_FLOW : flow_stats_reply.unpack,
@@ -234,14 +279,15 @@
     const.OFPST_TABLE : table_stats_reply.unpack,
     const.OFPST_PORT : port_stats_reply.unpack,
     const.OFPST_QUEUE : queue_stats_reply.unpack,
-:: if version >= of_g.VERSION_1_1:
+    const.OFPST_EXPERIMENTER : parse_experimenter_stats_reply,
+:: if version >= OFVersions.VERSION_1_1:
     const.OFPST_GROUP : group_stats_reply.unpack,
     const.OFPST_GROUP_DESC : group_desc_stats_reply.unpack,
 :: #endif
-:: if version >= of_g.VERSION_1_2:
+:: if version >= OFVersions.VERSION_1_2:
     const.OFPST_GROUP_FEATURES : group_features_stats_reply.unpack,
 :: #endif
-:: if version >= of_g.VERSION_1_3:
+:: if version >= OFVersions.VERSION_1_3:
     const.OFPST_METER : meter_stats_reply.unpack,
     const.OFPST_METER_CONFIG : meter_config_stats_reply.unpack,
     const.OFPST_METER_FEATURES : meter_features_stats_reply.unpack,
@@ -257,14 +303,15 @@
     const.OFPST_TABLE : table_stats_request.unpack,
     const.OFPST_PORT : port_stats_request.unpack,
     const.OFPST_QUEUE : queue_stats_request.unpack,
-:: if version >= of_g.VERSION_1_1:
+    const.OFPST_EXPERIMENTER : parse_experimenter_stats_request,
+:: if version >= OFVersions.VERSION_1_1:
     const.OFPST_GROUP : group_stats_request.unpack,
     const.OFPST_GROUP_DESC : group_desc_stats_request.unpack,
 :: #endif
-:: if version >= of_g.VERSION_1_2:
+:: if version >= OFVersions.VERSION_1_2:
     const.OFPST_GROUP_FEATURES : group_features_stats_request.unpack,
 :: #endif
-:: if version >= of_g.VERSION_1_3:
+:: if version >= OFVersions.VERSION_1_3:
     const.OFPST_METER : meter_stats_request.unpack,
     const.OFPST_METER_CONFIG : meter_config_stats_request.unpack,
     const.OFPST_METER_FEATURES : meter_features_stats_request.unpack,
@@ -286,3 +333,19 @@
     },
 :: #endfor
 }
+
+experimenter_stats_request_parsers = {
+    0x005c16c7: {
+:: if version >= OFVersions.VERSION_1_3:
+        1: bsn_lacp_stats_request.unpack,
+:: #endif
+    },
+}
+
+experimenter_stats_reply_parsers = {
+    0x005c16c7: {
+:: if version >= OFVersions.VERSION_1_3:
+        1: bsn_lacp_stats_reply.unpack,
+:: #endif
+    },
+}
diff --git a/py_gen/templates/meter_band.py b/py_gen/templates/meter_band.py
index eeb9ff2..37cc6d9 100644
--- a/py_gen/templates/meter_band.py
+++ b/py_gen/templates/meter_band.py
@@ -26,7 +26,6 @@
 :: # under the EPL.
 ::
 :: import itertools
-:: import of_g
 :: import py_gen.util as util
 :: include('_copyright.py')
 
diff --git a/py_gen/templates/oxm.py b/py_gen/templates/oxm.py
index 50eb4bd..ecb52ac 100644
--- a/py_gen/templates/oxm.py
+++ b/py_gen/templates/oxm.py
@@ -26,7 +26,6 @@
 :: # under the EPL.
 ::
 :: import itertools
-:: import of_g
 :: import py_gen.oftype
 :: include('_copyright.py')
 
diff --git a/py_gen/templates/toplevel_init.py b/py_gen/templates/toplevel_init.py
index b0ee86d..b170bd8 100644
--- a/py_gen/templates/toplevel_init.py
+++ b/py_gen/templates/toplevel_init.py
@@ -25,12 +25,15 @@
 :: # EPL for the specific language governing permissions and limitations
 :: # under the EPL.
 ::
-:: import of_g
 :: include('_copyright.py')
-
+:: import loxi_globals
 :: include('_autogen.py')
 
-version_names = ${repr(of_g.param_version_names)}
+version_names = {
+:: for v in loxi_globals.OFVersions.all_supported:
+    ${v.wire_version}: "${v.version}",
+:: #endfor
+}
 
 def protocol(ver):
     """
diff --git a/py_gen/templates/util.py b/py_gen/templates/util.py
index 1566e82..50a64db 100644
--- a/py_gen/templates/util.py
+++ b/py_gen/templates/util.py
@@ -26,7 +26,7 @@
 :: # under the EPL.
 ::
 :: include('_copyright.py')
-
+:: from loxi_globals import OFVersions
 :: include('_autogen.py')
 
 import loxi
@@ -52,7 +52,7 @@
         set_flags.append("%#x" % v)
     return '|'.join(set_flags) or '0'
 
-:: if version in [1,2]:
+:: if version in (OFVersions.VERSION_1_0, OFVersions.VERSION_1_1):
 def pretty_wildcards(v):
     if v == const.OFPFW_ALL:
         return 'OFPFW_ALL'
@@ -71,70 +71,70 @@
     return v
 
 def pack_port_no(value):
-:: if version == 1:
+:: if version == OFVersions.VERSION_1_0:
     return struct.pack("!H", value)
 :: else:
     return struct.pack("!L", value)
 :: #endif
 
 def unpack_port_no(reader):
-:: if version == 1:
+:: if version == OFVersions.VERSION_1_0:
     return reader.read("!H")[0]
 :: else:
     return reader.read("!L")[0]
 :: #endif
 
 def pack_fm_cmd(value):
-:: if version == 1:
+:: if version == OFVersions.VERSION_1_0:
     return struct.pack("!H", value)
 :: else:
     return struct.pack("!B", value)
 :: #endif
 
 def unpack_fm_cmd(reader):
-:: if version == 1:
+:: if version == OFVersions.VERSION_1_0:
     return reader.read("!H")[0]
 :: else:
     return reader.read("!B")[0]
 :: #endif
 
 def init_wc_bmap():
-:: if version <= 2:
+:: if version <= OFVersions.VERSION_1_1:
     return const.OFPFW_ALL
 :: else:
     return 0
 :: #endif
 
 def pack_wc_bmap(value):
-:: if version <= 2:
+:: if version <= OFVersions.VERSION_1_1:
     return struct.pack("!L", value)
 :: else:
     return struct.pack("!Q", value)
 :: #endif
 
 def unpack_wc_bmap(reader):
-:: if version <= 2:
+:: if version <= OFVersions.VERSION_1_1:
     return reader.read("!L")[0]
 :: else:
     return reader.read("!Q")[0]
 :: #endif
 
 def init_match_bmap():
-:: if version <= 2:
+:: if version <= OFVersions.VERSION_1_1:
     return const.OFPFW_ALL
 :: else:
     return 0
 :: #endif
 
 def pack_match_bmap(value):
-:: if version <= 2:
+:: if version <= OFVersions.VERSION_1_1:
     return struct.pack("!L", value)
 :: else:
     return struct.pack("!Q", value)
 :: #endif
 
 def unpack_match_bmap(reader):
-:: if version <= 2:
+:: if version <= OFVersions.VERSION_1_1:
     return reader.read("!L")[0]
 :: else:
     return reader.read("!Q")[0]
diff --git a/py_gen/util.py b/py_gen/util.py
index f527964..b405441 100644
--- a/py_gen/util.py
+++ b/py_gen/util.py
@@ -30,19 +30,20 @@
 """
 
 import os
-import of_g
+import loxi_globals
+import template_utils
 import loxi_utils.loxi_utils as utils
 
 templates_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'templates')
 
 def render_template(out, name, **context):
-    utils.render_template(out, name, [templates_dir], context)
+    template_utils.render_template(out, name, [templates_dir], context)
 
 def render_static(out, name):
-    utils.render_static(out, name, [templates_dir])
+    template_utils.render_static(out, name, [templates_dir])
 
 def constant_for_value(version, group, value):
-    enums = of_g.ir[version].enums
+    enums = loxi_globals.ir[version].enums
     enum = [x for x in enums if x.name == group][0]
     for name, value2 in enum.values:
         if value == value2:
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..de01cc1
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,3 @@
+[nosetests]
+where = utest
+exe = True
diff --git a/template_utils.py b/template_utils.py
new file mode 100644
index 0000000..1db0292
--- /dev/null
+++ b/template_utils.py
@@ -0,0 +1,90 @@
+# Copyright 2013, Big Switch Networks, Inc.
+#
+# LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
+# the following special exception:
+#
+# LOXI Exception
+#
+# As a special exception to the terms of the EPL, you may distribute libraries
+# generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
+# that copyright and licensing notices generated by LoxiGen are not altered or removed
+# from the LoxiGen Libraries and the notice provided below is (i) included in
+# the LoxiGen Libraries, if distributed in source code form and (ii) included in any
+# documentation for the LoxiGen Libraries, if distributed in binary form.
+#
+# Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
+#
+# You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
+# a copy of the EPL at:
+#
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# EPL for the specific language governing permissions and limitations
+# under the EPL.
+
+import os
+import sys
+
+import tenjin
+
+""" @brief utilities for rendering templates
+"""
+
+def render_template(out, name, path, context, prefix = None):
+    """
+    Render a template using tenjin.
+    out: a file-like object
+    name: name of the template
+    path: array of directories to search for the template
+    context: dictionary of variables to pass to the template
+    prefix: optional prefix to use for embedding (for other languages than python)
+    """
+    pp = [ tenjin.PrefixedLinePreprocessor(prefix=prefix) if prefix else tenjin.PrefixedLinePreprocessor() ] # support "::" syntax
+    template_globals = { "to_str": str, "escape": str } # disable HTML escaping
+    engine = TemplateEngine(path=path, pp=pp)
+    out.write(engine.render(name, context, template_globals))
+
+def render_static(out, name, path):
+    """
+    Write out a static template.
+    out: a file-like object
+    name: name of the template
+    path: array of directories to search for the template
+    """
+    # Reuse the tenjin logic for finding the template
+    template_filename = tenjin.FileSystemLoader().find(name, path)
+    if not template_filename:
+        raise ValueError("template %s not found" % name)
+    with open(template_filename) as infile:
+        out.write(infile.read())
+
+class TemplateEngine(tenjin.Engine):
+    def include(self, template_name, **kwargs):
+        """
+        Tenjin has an issue with nested includes that use the same local variable
+        names, because it uses the same context dict for each level of nesting.
+        The fix is to copy the context.
+        """
+        frame = sys._getframe(1)
+        locals  = frame.f_locals
+        globals = frame.f_globals
+        context = locals["_context"].copy()
+        context.update(kwargs)
+        template = self.get_template(template_name, context, globals)
+        return template.render(context, globals, _buf=locals["_buf"])
+
+def open_output(install_dir, name):
+    """
+    Open an output file for writing
+
+    'name' may include slashes. Subdirectories will be automatically created.
+    """
+    print "Writing %s" % name
+    path = os.path.join(install_dir, name)
+    dirpath = os.path.dirname(path)
+    if not os.path.exists(dirpath):
+        os.makedirs(dirpath)
+    return open(path, "w")
diff --git a/test_data/of13/bsn_lacp_stats_reply.data b/test_data/of13/bsn_lacp_stats_reply.data
new file mode 100644
index 0000000..3e12237
--- /dev/null
+++ b/test_data/of13/bsn_lacp_stats_reply.data
@@ -0,0 +1,72 @@
+-- binary
+04 13 # version, type
+00 3c # length
+12 34 56 78 # xid
+ff ff # stats_type
+00 00 # flags
+00 00 00 00 # pad
+00 5c 16 c7 # experimenter
+00 00 00 1 # subtype
+00 00 f1 11 # entries[0].port_no
+f2 22 # entries[0].actor_sys_priority
+01 02 03 04 05 06 # entries[0].actor_sys_mac
+f3 33 # entries[0].actor_port_priority
+f4 44 # entries[0].actor_port_num
+f5 55 # entries[0].actor_port_key
+02 # entries[0].convergence_status
+00 # pad
+f6 66 # entries[0].partner_sys_priority
+0a 0b 0c 0d 0e 0f # entries[0].partner_sys_mac
+f7 77 # entries[0].partner_port_priority
+f8 88 # entries[0].partner_port_num
+f9 99 # entries[0].partner_port_key
+00 00 # pad
+-- python
+ofp.message.bsn_lacp_stats_reply(
+    xid=0x12345678,
+    flags=0,
+    entries=[
+        ofp.bsn_lacp_stats_entry(
+            port_no=0xf111,
+            actor_sys_priority=0xf222,
+            actor_sys_mac=[1, 2, 3, 4, 5, 6],
+            actor_port_priority=0xf333,
+            actor_port_num=0xf444,
+            actor_key=0xf555,
+            convergence_status=ofp.LACP_OUT_OF_SYNC,
+            partner_sys_priority=0xf666,
+            partner_sys_mac=[0xa, 0xb, 0xc, 0xd, 0xe, 0xf],
+            partner_port_priority=0xf777,
+            partner_port_num=0xf888,
+            partner_key=0xf999)])
+-- c
+obj = of_bsn_lacp_stats_reply_new(OF_VERSION_1_3);
+of_bsn_lacp_stats_reply_xid_set(obj, 0x12345678);
+{
+    of_object_t *entries = of_list_bsn_lacp_stats_entry_new(OF_VERSION_1_3);
+    {
+        of_object_t *elem = of_bsn_lacp_stats_entry_new(OF_VERSION_1_3);
+        of_bsn_lacp_stats_entry_port_no_set(elem, 0xf111);
+        of_bsn_lacp_stats_entry_actor_sys_priority_set(elem, 0xf222);
+        {
+            of_mac_addr_t mac = { { 1, 2, 3, 4, 5, 6 } };
+            of_bsn_lacp_stats_entry_actor_sys_mac_set(elem, mac);
+        }
+        of_bsn_lacp_stats_entry_actor_port_priority_set(elem, 0xf333);
+        of_bsn_lacp_stats_entry_actor_port_num_set(elem, 0xf444);
+        of_bsn_lacp_stats_entry_actor_key_set(elem, 0xf555);
+        of_bsn_lacp_stats_entry_partner_sys_priority_set(elem, 0xf666);
+        of_bsn_lacp_stats_entry_convergence_status_set(elem, LACP_OUT_OF_SYNC);
+        {
+            of_mac_addr_t mac = { { 0xa, 0xb, 0xc, 0xd, 0xe, 0xf } };
+            of_bsn_lacp_stats_entry_partner_sys_mac_set(elem, mac);
+        }
+        of_bsn_lacp_stats_entry_partner_port_priority_set(elem, 0xf777);
+        of_bsn_lacp_stats_entry_partner_port_num_set(elem, 0xf888);
+        of_bsn_lacp_stats_entry_partner_key_set(elem, 0xf999);
+        of_list_append(entries, elem);
+        of_object_delete(elem);
+    }
+    of_bsn_lacp_stats_reply_entries_set(obj, entries);
+    of_object_delete(entries);
+}
diff --git a/test_data/of13/bsn_lacp_stats_request.data b/test_data/of13/bsn_lacp_stats_request.data
new file mode 100644
index 0000000..34aaf94
--- /dev/null
+++ b/test_data/of13/bsn_lacp_stats_request.data
@@ -0,0 +1,18 @@
+-- binary
+04 12 # version, type
+00 18 # length
+12 34 56 78 # xid
+ff ff # stats_type
+00 00 # flags
+00 00 00 00 # pad
+00 5c 16 c7 # experimenter
+00 00 00 1 # subtype
+-- python
+ofp.message.bsn_lacp_stats_request(
+    xid=0x12345678,
+    flags=0)
+-- java
+builder.setXid(0x12345678)
+-- c
+obj = of_bsn_lacp_stats_request_new(OF_VERSION_1_3);
+of_bsn_lacp_stats_request_xid_set(obj, 0x12345678);
diff --git a/test_data/of13/group_mod.data b/test_data/of13/group_modify.data
similarity index 65%
rename from test_data/of13/group_mod.data
rename to test_data/of13/group_modify.data
index 8f25b57..87eee29 100644
--- a/test_data/of13/group_mod.data
+++ b/test_data/of13/group_modify.data
@@ -37,9 +37,8 @@
 00 00 # buckets[1].actions[1].max_len
 00 00 00 00 00 00 # pad
 -- python
-ofp.message.group_mod(
+ofp.message.group_modify(
     xid=0x12345678,
-    command=ofp.OFPGC_MODIFY,
     group_type=ofp.OFPGT_FF,
     group_id=5,
     buckets=[
@@ -57,3 +56,31 @@
             actions=[
                 ofp.action.output(port=5, max_len=0),
                 ofp.action.output(port=6, max_len=0)])])
+-- java
+    OFActions actions = factory.actions();
+    builder
+      .setXid(0x12345678)
+      .setGroupType(OFGroupType.FF)
+      .setGroup(OFGroup.of(5))
+      .setBuckets(ImmutableList.<OFBucket>of(
+        factory.buildBucket()
+          .setWeight(1)
+          .setWatchPort(OFPort.of(5))
+          .setWatchGroup(OFGroup.ANY)
+          .setActions(ImmutableList.<OFAction>of(
+            actions.output(OFPort.of(5), 0),
+            actions.output(OFPort.of(6), 0)
+          ))
+          .build(),
+        factory.buildBucket()
+          .setWeight(1)
+          .setWatchPort(OFPort.of(6))
+          .setWatchGroup(OFGroup.ANY)
+          .setActions(ImmutableList.<OFAction>of(
+            actions.output(OFPort.of(5), 0),
+            actions.output(OFPort.of(6), 0)
+          ))
+          .build()
+         )
+      )
+      .build();
diff --git a/utest/test_build_ir.py b/utest/test_build_ir.py
new file mode 100755
index 0000000..b6d87a3
--- /dev/null
+++ b/utest/test_build_ir.py
@@ -0,0 +1,232 @@
+#!/usr/bin/env python
+# Copyright 2013, Big Switch Networks, Inc.
+#
+# LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
+# the following special exception:
+#
+# LOXI Exception
+#
+# As a special exception to the terms of the EPL, you may distribute libraries
+# generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
+# that copyright and licensing notices generated by LoxiGen are not altered or removed
+# from the LoxiGen Libraries and the notice provided below is (i) included in
+# the LoxiGen Libraries, if distributed in source code form and (ii) included in any
+# documentation for the LoxiGen Libraries, if distributed in binary form.
+#
+# Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
+#
+# You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
+# a copy of the EPL at:
+#
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# EPL for the specific language governing permissions and limitations
+# under the EPL.
+
+import sys
+import os
+import unittest
+
+from nose.tools import eq_, ok_, raises
+
+root_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..')
+sys.path.insert(0, root_dir)
+
+import loxi_ir.ir as ir
+import loxi_front_end.frontend_ir as fe
+
+class BuildIRTest(unittest.TestCase):
+
+    def test_simple(self):
+        version = ir.OFVersion("1.0", 1)
+        input = fe.OFInput(filename="test.dat",
+                    wire_versions=(1,),
+                    classes=(
+                      fe.OFClass(name="OFMessage",
+                                 superclass=None,
+                                 members=(
+                                     fe.OFDataMember(name='version', oftype='uint32_t'),
+                                     fe.OFLengthMember(name='length', oftype='uint16_t')
+                                 ),
+                                 virtual=False,
+                                 params={}
+                      ),
+                    ),
+                    enums=()
+                )
+
+        p = ir.build_protocol(version, [ input ])
+        eq_(1, len(p.classes))
+        c = p.classes[0]
+        eq_("OFMessage", c.name)
+        eq_(None, c.superclass)
+        eq_(False, c.virtual)
+        eq_({}, c.params)
+        eq_(2, len(c.members))
+        eq_(p, c.protocol)
+
+        m1 = c.members[0]
+        ok_(isinstance(m1, ir.OFDataMember))
+        eq_("version", m1.name)
+        eq_("uint32_t", m1.oftype)
+        eq_(4, m1.length)
+        eq_(True, m1.is_fixed_length)
+        eq_(0, m1.offset)
+        eq_(c, m1.of_class)
+
+        m2 = c.members[1]
+        ok_(isinstance(m2, ir.OFLengthMember))
+        eq_("length", m2.name)
+        eq_("uint16_t", m2.oftype)
+        eq_(2, m2.length)
+        eq_(True, m2.is_fixed_length)
+        eq_(4, m2.offset)
+        eq_(c, m2.of_class)
+
+        eq_(True, c.is_fixed_length)
+        eq_(6, c.length)
+
+    def test_resolve_superclass(self):
+        version = ir.OFVersion("1.0", 1)
+        input = fe.OFInput(filename="test.dat",
+                    wire_versions=(1,),
+                    classes=(
+                      fe.OFClass(name="OFMessage",
+                                 superclass=None,
+                                 members=(),
+                                 virtual=True,
+                                 params={}
+                      ),
+                      fe.OFClass(name="OFHello",
+                                 superclass="OFMessage",
+                                 members=(),
+                                 virtual=False,
+                                 params={}
+                      ),
+                    ),
+                    enums=()
+                )
+        p = ir.build_protocol(version, [ input ])
+        eq_(2, len(p.classes))
+        c, c2 = p.classes
+        eq_("OFMessage", c.name)
+        eq_(None, c.superclass)
+        eq_(True, c.virtual)
+        eq_("OFHello", c2.name)
+        eq_(c, c2.superclass)
+        eq_(False, c2.virtual)
+
+    @raises(ir.ClassNotFoundException)
+    def test_resolve_superclass(self):
+        version = ir.OFVersion("1.0", 1)
+        input = fe.OFInput(filename="test.dat",
+                    wire_versions=(1,),
+                    classes=(
+                      fe.OFClass(name="OFMessage",
+                                 superclass="NotFoundSuperClass",
+                                 members=(),
+                                 virtual=True,
+                                 params={}
+                      ),
+                   ),
+                    enums=()
+                )
+        p = ir.build_protocol(version, [ input ])
+
+
+    @raises(ir.DependencyCycleException)
+    def test_dependency_cycle(self):
+        version = ir.OFVersion("1.0", 1)
+        input = fe.OFInput(filename="test.dat",
+                    wire_versions=(1,),
+                    classes=(
+                      fe.OFClass(name="OFMessage",
+                                 superclass="OFHeader",
+                                 members=(),
+                                 virtual=True,
+                                 params={}
+                      ),
+                       fe.OFClass(name="OFHeader",
+                                 superclass="OFMessage",
+                                 members=(),
+                                 virtual=True,
+                                 params={}
+                      ),
+                   ),
+                    enums=()
+                )
+        p = ir.build_protocol(version, [ input ])
+
+    @raises(ir.RedefinedException)
+    def test_class_redefined(self):
+        version = ir.OFVersion("1.0", 1)
+        inputs = (
+            fe.OFInput(filename="test.dat",
+                    wire_versions=(1,),
+                    classes=(
+                      fe.OFClass(name="OFMessage",
+                                 superclass=None,
+                                 members=(),
+                                 virtual=True,
+                                 params={}
+                      ),
+                   ),
+                    enums=(),
+             ),
+             fe.OFInput(filename="test2.dat",
+                    wire_versions=(1,),
+                    classes=(
+                      fe.OFClass(name="OFMessage",
+                                 superclass=None,
+                                 members=(),
+                                 virtual=True,
+                                 params={}
+                      ),
+                  ),
+                    enums=()
+                )
+        )
+        p = ir.build_protocol(version, inputs)
+
+
+    def test_enums(self):
+        version = ir.OFVersion("1.0", 1)
+        input = fe.OFInput(filename="test.dat",
+                    wire_versions=(1,),
+                    classes=(),
+                    enums=(
+                        fe.OFEnum(name='ofp_flow_wildcards',
+                                  entries=(fe.OFEnumEntry(name="OFPFW_IN_PORT", value=0x01, params={}),
+                                           fe.OFEnumEntry(name="OFPFW_DL_VLAN", value=0x2, params={})),
+                                  params = dict(wire_type="uint32_t", bitmask=True)
+                                 ),
+                        fe.OFEnum(name='ofp_queue_properties',
+                                  entries=(fe.OFEnumEntry(name="OFPQT_NONE", value=0x00, params={}),
+                                           fe.OFEnumEntry(name="OFPQT_MIN_RATE", value=0x1, params={})),
+                                  params = dict(wire_type="uint32_t")
+                                 ),
+                        )
+                    )
+
+        p = ir.build_protocol(version, [ input ])
+        eq_(0, len(p.classes))
+        eq_(2, len(p.enums))
+        e = p.enums[0]
+        eq_("ofp_flow_wildcards", e.name)
+        eq_(True, e.is_bitmask)
+        eq_("uint32_t", e.wire_type)
+        eq_(ir.OFEnumEntry(name="OFPFW_IN_PORT", value=0x01, params={}), e.entries[0])
+        eq_(ir.OFEnumEntry(name="OFPFW_DL_VLAN", value=0x02, params={}), e.entries[1])
+
+        e = p.enums[1]
+        eq_("ofp_queue_properties", e.name)
+        eq_(False, e.is_bitmask)
+        eq_("uint32_t", e.wire_type)
+        eq_(ir.OFEnumEntry(name="OFPQT_NONE", value=0x00, params={}), e.entries[0])
+        eq_(ir.OFEnumEntry(name="OFPQT_MIN_RATE", value=0x01, params={}), e.entries[1])
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/utest/test_frontend.py b/utest/test_frontend.py
index 8febfde..cfadc39 100755
--- a/utest/test_frontend.py
+++ b/utest/test_frontend.py
@@ -35,7 +35,7 @@
 
 import loxi_front_end.parser as parser
 import loxi_front_end.frontend as frontend
-from loxi_ir import *
+from loxi_front_end.frontend_ir import *
 
 class FrontendTests(unittest.TestCase):
     maxDiff = None
@@ -109,7 +109,7 @@
         ]
         self.assertEquals(expected_ast, ast)
 
-        ofinput = frontend.create_ofinput(ast)
+        ofinput = frontend.create_ofinput("standard-1.0", ast)
         self.assertEquals(set([1, 2]), ofinput.wire_versions)
         expected_classes = [
             OFClass(name='of_echo_reply', superclass=None, members=[
@@ -180,7 +180,7 @@
         ]
         self.assertEquals(expected_ast, ast)
 
-        ofinput = frontend.create_ofinput(ast)
+        ofinput = frontend.create_ofinput("standard-1.0", ast)
         expected_classes = [
             OFClass(name='of_queue_prop', superclass=None, members=[
                 OFDiscriminatorMember('type', 'uint16_t'),
diff --git a/wireshark_gen/__init__.py b/wireshark_gen/__init__.py
index 1225131..5c02e06 100644
--- a/wireshark_gen/__init__.py
+++ b/wireshark_gen/__init__.py
@@ -29,17 +29,18 @@
 from collections import namedtuple
 import loxi_utils.loxi_utils as utils
 import loxi_front_end
-import of_g
+import loxi_globals
 from loxi_ir import *
 import field_info
+import template_utils
 
 templates_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'templates')
 
 DissectorField = namedtuple("DissectorField", ["fullname", "name", "type", "base", "enum_table"])
 
 proto_names = { 1: 'of10', 2: 'of11', 3: 'of12', 4: 'of13' }
-def make_field_name(wire_version, ofclass_name, member_name):
-    return "%s.%s.%s" % (proto_names[wire_version],
+def make_field_name(version, ofclass_name, member_name):
+    return "%s.%s.%s" % (proto_names[version.wire_version],
                          ofclass_name[3:],
                          member_name)
 
@@ -47,7 +48,7 @@
     """
     Decide on a reader function to use for the given field
     """
-    ofproto = of_g.ir[version]
+    ofproto = loxi_globals.ir[version]
     enum = ofproto.enum_by_name(m.oftype)
     if enum and 'wire_type' in enum.params:
         return "read_" + enum.params['wire_type']
@@ -63,7 +64,7 @@
     if oftype.startswith("list"):
         return "bytes", "NONE", "nil"
 
-    ofproto = of_g.ir[version]
+    ofproto = loxi_globals.ir[version]
 
     enum = ofproto.enum_by_name(oftype)
     if not enum and (cls, name) in field_info.class_field_to_enum:
@@ -92,7 +93,7 @@
         field_base = "NONE"
 
     if enum:
-        enum_table = 'enum_v%d_%s' % (version, enum.name)
+        enum_table = 'enum_v%d_%s' % (version.wire_version, enum.name)
     else:
         enum_table = 'nil'
 
@@ -100,21 +101,21 @@
 
 def create_fields():
     r = []
-    for wire_version, ofproto in of_g.ir.items():
+    for version, ofproto in loxi_globals.ir.items():
         for ofclass in ofproto.classes:
             for m in ofclass.members:
                 if isinstance(m, OFPadMember):
                     continue
-                fullname = make_field_name(wire_version, ofclass.name, m.name)
-                field_type, field_base, enum_table = get_field_info(wire_version, ofclass.name, m.name, m.oftype)
+                fullname = make_field_name(version, ofclass.name, m.name)
+                field_type, field_base, enum_table = get_field_info(version, ofclass.name, m.name, m.oftype)
                 r.append(DissectorField(fullname, m.name, field_type, field_base, enum_table))
 
     return r
 
-def generate():
+def generate(install_dir):
     context = {
         'fields': create_fields(),
     }
 
-    with utils.open_output('openflow.lua') as out:
-        utils.render_template(out, "openflow.lua", [templates_dir], context)
+    with template_utils.open_output(install_dir, 'wireshark/openflow.lua') as out:
+        template_utils.render_template(out, "openflow.lua", [templates_dir], context)
diff --git a/wireshark_gen/templates/_ofclass_dissector.lua b/wireshark_gen/templates/_ofclass_dissector.lua
index 4aef886..0eb45eb 100644
--- a/wireshark_gen/templates/_ofclass_dissector.lua
+++ b/wireshark_gen/templates/_ofclass_dissector.lua
@@ -33,7 +33,7 @@
 :: if not ofclass.superclass: attrs.append("top-level")
 -- ${' '.join(attrs)} class ${ofclass.name}
 :: if ofclass.superclass:
--- Child of ${ofclass.superclass}
+-- Child of ${ofclass.superclass.name}
 :: #endif
 :: if ofclass.virtual:
 -- Discriminator is ${ofclass.discriminator.name}
@@ -46,7 +46,7 @@
 :: #endif
 :: field_name = make_field_name(version, ofclass.name, m.name)
 :: reader_name = get_reader(version, ofclass, m)
-    ${reader_name}(reader, ${version}, subtree, '${field_name}')
+    ${reader_name}(reader, ${version.wire_version}, subtree, '${field_name}')
 :: #endfor
     return '${ofclass.name}'
 end
diff --git a/wireshark_gen/templates/openflow.lua b/wireshark_gen/templates/openflow.lua
index 841e502..7113281 100644
--- a/wireshark_gen/templates/openflow.lua
+++ b/wireshark_gen/templates/openflow.lua
@@ -25,8 +25,8 @@
 :: # EPL for the specific language governing permissions and limitations
 :: # under the EPL.
 ::
-:: import of_g
-:: ir = of_g.ir
+:: import loxi_globals
+:: ir = loxi_globals.ir
 :: include('_copyright.lua')
 
 :: include('_ofreader.lua')
@@ -36,14 +36,14 @@
 p_of = Proto ("of", "OpenFlow")
 
 local openflow_versions = {
-:: for (version, name) in of_g.param_version_names.items():
-    [${version}] = "${name}",
+:: for version in loxi_globals.OFVersions.all_supported:
+    [${version.wire_version}] = "${version.version}",
 :: #endfor
 }
 
 :: for version, ofproto in ir.items():
 :: for enum in ofproto.enums:
-local enum_v${version}_${enum.name} = {
+local enum_v${version.wire_version}_${enum.name} = {
 :: for (name, value) in enum.values:
     [${value}] = "${name}",
 :: #endfor
@@ -75,7 +75,7 @@
 :: for version, ofproto in ir.items():
 :: for ofclass in ofproto.classes:
 :: if ofclass.virtual:
-${ofclass.name}_v${version}_dissectors = {}
+${ofclass.name}_v${version.wire_version}_dissectors = {}
 :: #endif
 :: #endfor
 :: #endfor
@@ -84,12 +84,12 @@
 
 :: for version, ofproto in ir.items():
 :: for ofclass in ofproto.classes:
-:: name = 'dissect_%s_v%d' % (ofclass.name, version)
+:: name = 'dissect_%s_v%d' % (ofclass.name, version.wire_version)
 :: include('_ofclass_dissector.lua', name=name, ofclass=ofclass, version=version)
 :: if ofclass.superclass:
-:: discriminator = ofproto.class_by_name(ofclass.superclass).discriminator
+:: discriminator = ofclass.superclass.discriminator
 :: discriminator_value = ofclass.member_by_name(discriminator.name).value
-${ofclass.superclass}_v${version}_dissectors[${discriminator_value}] = ${name}
+${ofclass.superclass.name}_v${version.wire_version}_dissectors[${discriminator_value}] = ${name}
 
 :: #endif
 :: #endfor
@@ -97,73 +97,73 @@
 
 local of_message_dissectors = {
 :: for version in ir:
-    [${version}] = of_header_v${version}_dissectors,
+    [${version.wire_version}] = of_header_v${version.wire_version}_dissectors,
 :: #endfor
 }
 
 local of_oxm_dissectors = {
 :: for version in ir:
-    [${version}] = of_oxm_v${version}_dissectors,
+    [${version.wire_version}] = of_oxm_v${version.wire_version}_dissectors,
 :: #endfor
 }
 
 local of_action_dissectors = {
 :: for version in ir:
-    [${version}] = of_action_v${version}_dissectors,
+    [${version.wire_version}] = of_action_v${version.wire_version}_dissectors,
 :: #endfor
 }
 
 local of_instruction_dissectors = {
 :: for version in ir:
-    [${version}] = of_instruction_v${version}_dissectors,
+    [${version.wire_version}] = of_instruction_v${version.wire_version}_dissectors,
 :: #endfor
 }
 
 local of_bucket_dissectors = {
 :: for version in ir:
-    [${version}] = dissect_of_bucket_v${version},
+    [${version.wire_version}] = dissect_of_bucket_v${version.wire_version},
 :: #endfor
 }
 
 local of_port_desc_dissectors = {
 :: for version in ir:
-    [${version}] = dissect_of_port_desc_v${version},
+    [${version.wire_version}] = dissect_of_port_desc_v${version.wire_version},
 :: #endfor
 }
 
 local of_stats_reply_dissectors = {
 :: for version in ir:
-    [${version}] = of_stats_reply_v${version}_dissectors,
+    [${version.wire_version}] = of_stats_reply_v${version.wire_version}_dissectors,
 :: #endfor
 }
 
 local of_stats_request_dissectors = {
 :: for version in ir:
-    [${version}] = of_stats_request_v${version}_dissectors,
+    [${version.wire_version}] = of_stats_request_v${version.wire_version}_dissectors,
 :: #endfor
 }
 
 local of_flow_stats_entry_dissectors = {
 :: for version in ir:
-    [${version}] = dissect_of_flow_stats_entry_v${version},
+    [${version.wire_version}] = dissect_of_flow_stats_entry_v${version.wire_version},
 :: #endfor
 }
 
 local of_port_stats_entry_dissectors = {
 :: for version in ir:
-    [${version}] = dissect_of_port_stats_entry_v${version},
+    [${version.wire_version}] = dissect_of_port_stats_entry_v${version.wire_version},
 :: #endfor
 }
 
 local of_table_stats_entry_dissectors = {
 :: for version in ir:
-    [${version}] = dissect_of_table_stats_entry_v${version},
+    [${version.wire_version}] = dissect_of_table_stats_entry_v${version.wire_version},
 :: #endfor
 }
 
 local of_queue_stats_entry_dissectors = {
 :: for version in ir:
-    [${version}] = dissect_of_queue_stats_entry_v${version},
+    [${version.wire_version}] = dissect_of_queue_stats_entry_v${version.wire_version},
 :: #endfor
 }