loxi_ir: Moved to its own package

to group ir and related functionality. Also introduce a dedicated
frontend_ir model, and adds a unit test
diff --git a/loxi_front_end/frontend.py b/loxi_front_end/frontend.py
index f4dece5..1e1b1de 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])
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_ir/__init__.py b/loxi_ir/__init__.py
new file mode 100644
index 0000000..b4f1c05
--- /dev/null
+++ b/loxi_ir/__init__.py
@@ -0,0 +1,31 @@
+# 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 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..21f760c
--- /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: {}".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(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..24589b2
--- /dev/null
+++ b/loxi_ir/ir_offset.py
@@ -0,0 +1,196 @@
+## 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
+
+def calculate_offsets_and_lengths(protocol):
+    for ir_class in protocol.classes:
+        offset_and_length_for_class(ir_class)
diff --git a/loxi_ir/unified.py b/loxi_ir/unified.py
new file mode 100644
index 0000000..d481730
--- /dev/null
+++ b/loxi_ir/unified.py
@@ -0,0 +1,155 @@
+#!/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, unified_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 {} - 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
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'),