Merge into master from pull request #147:
Make pyloxi inheritance and list handling generic (https://github.com/floodlight/loxigen/pull/147)
diff --git a/loxi_front_end/frontend.py b/loxi_front_end/frontend.py
index a84508a..545b5bc 100644
--- a/loxi_front_end/frontend.py
+++ b/loxi_front_end/frontend.py
@@ -54,6 +54,8 @@
         elif m_ast[2] == 'actions_len':
             # HACK only usage so far
             return ir.OFFieldLengthMember(name=m_ast[2], oftype=get_type(m_ast[1], ctx), field_name='actions')
+        if m_ast[2] == 'version': # Should be moved to parser
+            return ir.OFVersionMember(name=m_ast[2], oftype=get_type(m_ast[1], ctx))
         else:
             return ir.OFDataMember(name=m_ast[2], oftype=get_type(m_ast[1], ctx))
     elif m_ast[0] == 'discriminator':
diff --git a/loxi_front_end/frontend_ir.py b/loxi_front_end/frontend_ir.py
index a927f94..af3f223 100644
--- a/loxi_front_end/frontend_ir.py
+++ b/loxi_front_end/frontend_ir.py
@@ -38,6 +38,7 @@
     'OFLengthMember',
     'OFFieldLengthMember',
     'OFPadMember',
+    'OFVersionMember',
     'OFEnum',
     'OFEnumEntry'
 ]
@@ -128,6 +129,16 @@
 OFPadMember = namedtuple('OFPadMember', ['length'])
 
 """
+Field with the version of an OpenFlow object
+
+@param name
+@param oftype C-like type string
+
+Example: hello.version
+"""
+OFVersionMember = namedtuple('OFVersionMember', ['name', 'oftype'])
+
+"""
 An OpenFlow enumeration
 
 All values are Python ints.
diff --git a/loxi_ir/ir.py b/loxi_ir/ir.py
index 8553f62..df1c77d 100644
--- a/loxi_ir/ir.py
+++ b/loxi_ir/ir.py
@@ -33,6 +33,7 @@
 from collections import namedtuple, OrderedDict
 from generic_utils import find, memoize, OrderedSet
 from loxi_ir import ir_offset
+import loxi_front_end.frontend_ir as frontend_ir
 
 logger = logging.getLogger(__name__)
 
@@ -174,6 +175,14 @@
         else:
             raise Exception("Not a fixed length class: {}".format(self.name))
 
+    @property
+    def has_internal_alignment(self):
+        return self.params.get('length_includes_align') == 'True'
+
+    @property
+    def has_external_alignment(self):
+        return self.params.get('length_includes_align') == 'False'
+
 """ one class unified across openflow versions. Keeps around a map version->versioned_class """
 class OFUnifiedClass(OFClass):
     def __new__(cls, version_classes, *a, **kw):
@@ -356,11 +365,18 @@
         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(fe_member._asdict()))
+        if isinstance(fe_member, frontend_ir.OFVersionMember):
+            member = OFTypeMember(offset = length_info.offset,
+                                  base_length = length_info.base_length,
+                                  is_fixed_length=length_info.is_fixed_length,
+                                  value = version.wire_version,
+                                  **convert_member_properties(fe_member._asdict()))
+        else:
+            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(fe_member._asdict()))
         member.of_class = of_class
         return member
 
diff --git a/py_gen/codegen.py b/py_gen/codegen.py
index 7cd068f..2c4a135 100644
--- a/py_gen/codegen.py
+++ b/py_gen/codegen.py
@@ -25,7 +25,7 @@
 # EPL for the specific language governing permissions and limitations
 # under the EPL.
 
-from collections import namedtuple
+from collections import defaultdict
 import loxi_globals
 import struct
 import template_utils
@@ -34,113 +34,76 @@
 import oftype
 from loxi_ir import *
 
-ofclasses_by_version = {}
+modules_by_version = {}
 
-PyOFClass = namedtuple('PyOFClass', ['name', 'pyname', 'members', 'type_members',
-                                     'min_length', 'is_fixed_length',
-                                     'has_internal_alignment', 'has_external_alignment'])
+# Map from inheritance root to module name
+roots = {
+    'of_header': 'message',
+    'of_action': 'action',
+    'of_oxm': 'oxm',
+    'of_instruction': 'instruction',
+    'of_meter_band': 'meter_band',
+}
 
-# Return the name for the generated Python class
-def generate_pyname(cls):
-    if utils.class_is_action(cls):
-        return cls[10:]
-    elif utils.class_is_oxm(cls):
-        return cls[7:]
-    elif utils.class_is_meter_band(cls):
-        return cls[14:]
-    elif utils.class_is_instruction(cls):
-        return cls[15:]
-    else:
-        return cls[3:]
+# Return the module and class names for the generated Python class
+def generate_pyname(ofclass):
+    for root, module_name in roots.items():
+        if ofclass.name == root:
+            return module_name, module_name
+        elif ofclass.is_instanceof(root):
+            if root == 'of_header':
+                # The input files don't prefix message names
+                return module_name, ofclass.name[3:]
+            else:
+                return module_name, ofclass.name[len(root)+1:]
+    return 'common', ofclass.name[3:]
 
 # Create intermediate representation, extended from the LOXI IR
-# HACK the oftype member attribute is replaced with an OFType instance
 def build_ofclasses(version):
-    ofclasses = []
+    modules = defaultdict(list)
     for ofclass in loxi_globals.ir[version].classes:
-        cls = ofclass.name
-        if ofclass.virtual:
-            continue
-
-        members = []
-        type_members = []
-
-        for m in ofclass.members:
-            if type(m) == OFTypeMember:
-                members.append(m)
-                type_members.append(members[-1])
-            elif type(m) == OFLengthMember:
-                members.append(m)
-            elif type(m) == OFFieldLengthMember:
-                members.append(m)
-            elif type(m) == OFPadMember:
-                members.append(m)
-            elif type(m) == OFDataMember:
-                if utils.class_is_message(ofclass.name) and m.name == 'version':
-                    # HACK move to frontend
-                    members.append(OFTypeMember(
-                        name=m.name,
-                        oftype=m.oftype,
-                        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)
-
-        ofclasses.append(
-            PyOFClass(name=cls,
-                      pyname=generate_pyname(cls),
-                      members=members,
-                      type_members=type_members,
-                      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
+        module_name, ofclass.pyname = generate_pyname(ofclass)
+        modules[module_name].append(ofclass)
+    return modules
 
 def generate_init(out, name, version):
     util.render_template(out, 'init.py', version=version)
 
 def generate_action(out, name, version):
-    ofclasses = [x for x in ofclasses_by_version[version]
-                 if utils.class_is_action(x.name)]
-    util.render_template(out, 'action.py', ofclasses=ofclasses, version=version)
+    util.render_template(out, 'module.py',
+                         ofclasses=modules_by_version[version]['action'],
+                         version=version)
 
 def generate_oxm(out, name, version):
-    ofclasses = [x for x in ofclasses_by_version[version]
-                 if utils.class_is_oxm(x.name)]
-    util.render_template(out, 'oxm.py', ofclasses=ofclasses, version=version)
+    util.render_template(out, 'module.py',
+                         ofclasses=modules_by_version[version]['oxm'],
+                         version=version)
 
 def generate_common(out, name, version):
-    ofclasses = [x for x in ofclasses_by_version[version]
-                 if not utils.class_is_message(x.name)
-                    and not utils.class_is_action(x.name)
-                    and not utils.class_is_instruction(x.name)
-                    and not utils.class_is_meter_band(x.name)
-                    and not utils.class_is_oxm(x.name)
-                    and not utils.class_is_list(x.name)]
-    util.render_template(out, 'common.py', ofclasses=ofclasses, version=version)
+    util.render_template(out, 'module.py',
+                         ofclasses=modules_by_version[version]['common'],
+                         version=version,
+                         extra_template='_common_extra.py')
 
 def generate_const(out, name, version):
     util.render_template(out, 'const.py', version=version,
                          enums=loxi_globals.ir[version].enums)
 
 def generate_instruction(out, name, version):
-    ofclasses = [x for x in ofclasses_by_version[version]
-                 if utils.class_is_instruction(x.name)]
-    util.render_template(out, 'instruction.py', ofclasses=ofclasses, version=version)
+    util.render_template(out, 'module.py',
+                         ofclasses=modules_by_version[version]['instruction'],
+                         version=version)
 
 def generate_message(out, name, version):
-    ofclasses = [x for x in ofclasses_by_version[version]
-                 if utils.class_is_message(x.name)]
-    util.render_template(out, 'message.py', ofclasses=ofclasses, version=version)
+    util.render_template(out, 'module.py',
+                         ofclasses=modules_by_version[version]['message'],
+                         version=version,
+                         extra_template='_message_extra.py')
 
 def generate_meter_band(out, name, version):
-    ofclasses = [x for x in ofclasses_by_version[version]
-                 if utils.class_is_meter_band(x.name)]
-    util.render_template(out, 'meter_band.py', ofclasses=ofclasses, version=version)
+    util.render_template(out, 'module.py',
+                         ofclasses=modules_by_version[version]['meter_band'],
+                         version=version)
 
 def generate_pp(out, name, version):
     util.render_template(out, 'pp.py')
@@ -150,4 +113,4 @@
 
 def init():
     for version in loxi_globals.OFVersions.target_versions:
-        ofclasses_by_version[version] = build_ofclasses(version)
+        modules_by_version[version] = build_ofclasses(version)
diff --git a/py_gen/oftype.py b/py_gen/oftype.py
index 605d5be..40bcb40 100644
--- a/py_gen/oftype.py
+++ b/py_gen/oftype.py
@@ -28,6 +28,8 @@
 from collections import namedtuple
 
 import loxi_utils.loxi_utils as loxi_utils
+import py_gen.codegen
+import loxi_globals
 
 OFTypeData = namedtuple("OFTypeData", ["init", "pack", "unpack"])
 
@@ -105,34 +107,10 @@
         pack='util.pack_bitmap_128(%s)',
         unpack="util.unpack_bitmap_128(%s)"),
 
-    # HACK need the match_v3 length field
-    'list(of_oxm_t)': OFTypeData(
-        init='[]',
-        pack='util.pack_list(%s)',
-        unpack='oxm.unpack_list(%s.slice(_length-4))'),
-
     'of_oxm_t': OFTypeData(
         init='None',
         pack='%s.pack()',
-        unpack='oxm.unpack(%s)'),
-
-    # TODO implement unpack
-    'list(of_table_features_t)': OFTypeData(
-        init='[]',
-        pack='util.pack_list(%s)',
-        unpack=None),
-
-    # TODO implement unpack
-    'list(of_action_id_t)': OFTypeData(
-        init='[]',
-        pack='util.pack_list(%s)',
-        unpack=None),
-
-    # TODO implement unpack
-    'list(of_table_feature_prop_t)': OFTypeData(
-        init='[]',
-        pack='util.pack_list(%s)',
-        unpack=None),
+        unpack='oxm.oxm.unpack(%s)'),
 }
 
 ## Fixed length strings
@@ -167,50 +145,11 @@
         pack='%s.pack()',
         unpack='%s.unpack(%%s)' % pyclass)
 
-## Variable element length lists
-
-# Map from list class name to list deserializer
-variable_elem_len_lists = {
-    'list(of_action_t)': 'action.unpack_list',
-    'list(of_bucket_t)': 'common.unpack_list_bucket',
-    'list(of_flow_stats_entry_t)': 'common.unpack_list_flow_stats_entry',
-    'list(of_group_desc_stats_entry_t)': 'common.unpack_list_group_desc_stats_entry',
-    'list(of_group_stats_entry_t)': 'common.unpack_list_group_stats_entry',
-    'list(of_hello_elem_t)': 'common.unpack_list_hello_elem',
-    'list(of_instruction_t)': 'instruction.unpack_list',
-    'list(of_meter_band_t)': 'meter_band.unpack_list',
-    'list(of_meter_stats_t)': 'common.unpack_list_meter_stats',
-    'list(of_packet_queue_t)': 'common.unpack_list_packet_queue',
-    'list(of_queue_prop_t)': 'common.unpack_list_queue_prop',
-}
-
-for (cls, deserializer) in variable_elem_len_lists.items():
-    type_data_map[cls] = OFTypeData(
-        init='[]',
-        pack='util.pack_list(%s)',
-        unpack='%s(%%s)' % deserializer)
-
-## Fixed element length lists
-
-# Map from list class name to list element deserializer
-fixed_elem_len_lists = {
-    'list(of_bsn_interface_t)': 'common.bsn_interface.unpack',
-    'list(of_bucket_counter_t)': 'common.bucket_counter.unpack',
-    'list(of_meter_band_stats_t)': 'common.meter_band_stats.unpack',
-    'list(of_port_desc_t)': 'common.port_desc.unpack',
-    'list(of_port_stats_entry_t)': 'common.port_stats_entry.unpack',
-    'list(of_queue_stats_entry_t)': 'common.queue_stats_entry.unpack',
-    '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():
-    type_data_map[cls] = OFTypeData(
-        init='[]',
-        pack='util.pack_list(%s)',
-        unpack='loxi.generic_util.unpack_list(%%s, %s)' % element_deserializer)
+# Special case for lists of hello_elem, which must ignore unknown types
+type_data_map['list(of_hello_elem_t)'] = OFTypeData(
+    init='[]',
+    pack='loxi.generic_util.pack_list(%s)',
+    unpack='util.unpack_list_hello_elem(%s)')
 
 ## Public interface
 
@@ -222,6 +161,8 @@
     type_data = lookup_type_data(oftype, version)
     if type_data and type_data.init:
         return type_data.init
+    elif oftype_is_list(oftype):
+        return "[]"
     else:
         return "loxi.unimplemented('init %s')" % oftype
 
@@ -233,6 +174,8 @@
     type_data = lookup_type_data(oftype, version)
     if type_data and type_data.pack:
         return type_data.pack % value_expr
+    elif oftype_is_list(oftype):
+        return "loxi.generic_util.pack_list(%s)" % value_expr
     else:
         return "loxi.unimplemented('pack %s')" % oftype
 
@@ -244,5 +187,19 @@
     type_data = lookup_type_data(oftype, version)
     if type_data and type_data.unpack:
         return type_data.unpack % reader_expr
+    elif oftype_is_list(oftype):
+        ofproto = loxi_globals.ir[version]
+        ofclass = ofproto.class_by_name(oftype_list_elem(oftype))
+        module_name, class_name = py_gen.codegen.generate_pyname(ofclass)
+        return 'loxi.generic_util.unpack_list(%s, %s.%s.unpack)' % \
+            (reader_expr, module_name, class_name)
     else:
         return "loxi.unimplemented('unpack %s')" % oftype
+
+def oftype_is_list(oftype):
+    return (oftype.find("list(") == 0)
+
+# Converts "list(of_flow_stats_entry_t)" to "of_flow_stats_entry"
+def oftype_list_elem(oftype):
+    assert oftype.find("list(") == 0
+    return oftype[5:-3]
diff --git a/py_gen/templates/_common_extra.py b/py_gen/templates/_common_extra.py
new file mode 100644
index 0000000..8639dc4
--- /dev/null
+++ b/py_gen/templates/_common_extra.py
@@ -0,0 +1,38 @@
+:: # 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_globals import OFVersions
+:: if version == OFVersions.VERSION_1_0:
+match = match_v1
+:: elif version == OFVersions.VERSION_1_1:
+match = match_v2
+:: elif version == OFVersions.VERSION_1_2:
+match = match_v3
+:: elif version == OFVersions.VERSION_1_3:
+:: # HACK
+match = match_v3
+:: #endif
diff --git a/py_gen/templates/_message_extra.py b/py_gen/templates/_message_extra.py
new file mode 100644
index 0000000..9fc2f5e
--- /dev/null
+++ b/py_gen/templates/_message_extra.py
@@ -0,0 +1,39 @@
+:: # 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.
+::
+def parse_header(buf):
+    if len(buf) < 8:
+        raise loxi.ProtocolError("too short to be an OpenFlow message")
+    return struct.unpack_from("!BBHL", buf)
+
+def parse_message(buf):
+    msg_ver, msg_type, msg_len, msg_xid = parse_header(buf)
+    if msg_ver != const.OFP_VERSION and msg_type != const.OFPT_HELLO:
+        raise loxi.ProtocolError("wrong OpenFlow version (expected %d, got %d)" % (const.OFP_VERSION, msg_ver))
+    if len(buf) != msg_len:
+        raise loxi.ProtocolError("incorrect message size")
+    return message.unpack(loxi.generic_util.OFReader(buf))
diff --git a/py_gen/templates/_ofclass.py b/py_gen/templates/_ofclass.py
index 695cce6..f902ae8 100644
--- a/py_gen/templates/_ofclass.py
+++ b/py_gen/templates/_ofclass.py
@@ -1,8 +1,10 @@
+:: superclass_pyname = ofclass.superclass.pyname if ofclass.superclass else "loxi.OFObject"
 :: from loxi_ir import *
 :: import py_gen.oftype
+:: type_members = [m for m in ofclass.members if type(m) == OFTypeMember]
 :: normal_members = [m for m in ofclass.members if type(m) == OFDataMember]
-class ${ofclass.pyname}(${superclass}):
-:: for m in ofclass.type_members:
+class ${ofclass.pyname}(${superclass_pyname}):
+:: for m in type_members:
     ${m.name} = ${m.value}
 :: #endfor
 
@@ -11,7 +13,12 @@
         if ${m.name} != None:
             self.${m.name} = ${m.name}
         else:
+:: if m.name == 'xid':
+:: # HACK for message xid
+            self.${m.name} = None
+:: else:
             self.${m.name} = ${py_gen.oftype.gen_init_expr(m.oftype, version=version)}
+:: #endif
 :: #endfor
         return
 
@@ -21,7 +28,7 @@
         return ''.join(packed)
 
     @staticmethod
-    def unpack(buf):
+    def unpack(reader):
         obj = ${ofclass.pyname}()
 :: include("_unpack.py", ofclass=ofclass)
         return obj
@@ -33,12 +40,12 @@
 :: #endfor
         return True
 
-    def __ne__(self, other):
-        return not self.__eq__(other)
-
-    def show(self):
-        import loxi.pp
-        return loxi.pp.pp(self)
-
     def pretty_print(self, q):
 :: include('_pretty_print.py', ofclass=ofclass)
+
+:: # Register with our superclass
+:: if ofclass.superclass:
+:: type_field_name = ofclass.superclass.discriminator.name
+:: type_value = ofclass.member_by_name(type_field_name).value
+${superclass_pyname}.subtypes[${type_value}] = ${ofclass.pyname}
+:: #endif
diff --git a/py_gen/templates/_unpack.py b/py_gen/templates/_unpack.py
index 81efb21..81f74c7 100644
--- a/py_gen/templates/_unpack.py
+++ b/py_gen/templates/_unpack.py
@@ -28,16 +28,14 @@
 :: # TODO coalesce format strings
 :: from loxi_ir import *
 :: from py_gen.oftype import gen_unpack_expr
-        if type(buf) == loxi.generic_util.OFReader:
-            reader = buf
-        else:
-            reader = loxi.generic_util.OFReader(buf)
 :: field_length_members = {}
 :: for m in ofclass.members:
 ::     if type(m) == OFPadMember:
         reader.skip(${m.length})
 ::     elif type(m) == OFLengthMember:
         _${m.name} = ${gen_unpack_expr(m.oftype, 'reader', version=version)}
+        orig_reader = reader
+        reader = orig_reader.slice(_${m.name} - (${m.offset} + ${m.length}))
 ::     elif type(m) == OFFieldLengthMember:
 ::         field_length_members[m.field_name] = m
         _${m.name} = ${gen_unpack_expr(m.oftype, 'reader', version=version)}
@@ -53,6 +51,6 @@
         obj.${m.name} = ${gen_unpack_expr(m.oftype, reader_expr, version=version)}
 ::     #endif
 :: #endfor
-:: if ofclass.has_external_alignment or ofclass.has_internal_alignment:
-        reader.skip_align()
+:: if ofclass.has_external_alignment:
+        orig_reader.skip_align()
 :: #endif
diff --git a/py_gen/templates/_virtual_ofclass.py b/py_gen/templates/_virtual_ofclass.py
new file mode 100644
index 0000000..1e5dcc2
--- /dev/null
+++ b/py_gen/templates/_virtual_ofclass.py
@@ -0,0 +1,23 @@
+:: import py_gen.util as util
+:: superclass_pyname = ofclass.superclass.pyname if ofclass.superclass else "loxi.OFObject"
+:: fmts = { 1: "B", 2: "!H", 4: "!L" }
+:: fmt = fmts[ofclass.discriminator.length]
+:: trail = ' '.join([x.pyname for x in util.ancestors(ofclass)])
+class ${ofclass.pyname}(${superclass_pyname}):
+    subtypes = {}
+
+    @staticmethod
+    def unpack(reader):
+        subtype, = reader.peek(${repr(fmt)}, ${ofclass.discriminator.offset})
+        try:
+            subclass = ${ofclass.pyname}.subtypes[subtype]
+        except KeyError:
+            raise loxi.ProtocolError("unknown ${trail} subtype %#x" % subtype)
+        return subclass.unpack(reader)
+
+:: # Register with our superclass
+:: if ofclass.superclass:
+:: type_field_name = ofclass.superclass.discriminator.name
+:: type_value = ofclass.member_by_name(type_field_name).value
+${superclass_pyname}.subtypes[${type_value}] = ${ofclass.pyname}
+:: #endif
diff --git a/py_gen/templates/action.py b/py_gen/templates/action.py
deleted file mode 100644
index 92c4e26..0000000
--- a/py_gen/templates/action.py
+++ /dev/null
@@ -1,100 +0,0 @@
-:: # 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 itertools
-:: from loxi_globals import OFVersions
-:: import py_gen.util as util
-:: include('_copyright.py')
-
-:: include('_autogen.py')
-
-import struct
-import const
-import util
-import loxi.generic_util
-import loxi
-:: if version >= OFVersions.VERSION_1_2:
-import oxm # for unpack
-:: #endif
-
-def unpack_list(reader):
-    def deserializer(reader, typ):
-        parser = parsers.get(typ)
-        if not parser: raise loxi.ProtocolError("unknown action type %d" % typ)
-        return parser(reader)
-    return loxi.generic_util.unpack_list_tlv16(reader, deserializer)
-
-class Action(object):
-    type = None # override in subclass
-    pass
-
-:: for ofclass in ofclasses:
-:: include('_ofclass.py', ofclass=ofclass, superclass="Action")
-
-:: #endfor
-
-def parse_experimenter(reader):
-    experimenter, = reader.peek("!4xL")
-    if experimenter == 0x005c16c7: # Big Switch Networks
-        subtype, = reader.peek("!8xL")
-    elif experimenter == 0x00002320: # Nicira
-        subtype, = reader.peek("!8xH")
-    else:
-        raise loxi.ProtocolError("unexpected experimenter id %#x" % experimenter)
-
-    if subtype in experimenter_parsers[experimenter]:
-        return experimenter_parsers[experimenter][subtype](reader)
-    else:
-        raise loxi.ProtocolError("unexpected BSN experimenter subtype %#x" % subtype)
-
-parsers = {
-:: sort_key = lambda x: x.type_members[0].value
-:: msgtype_groups = itertools.groupby(sorted(ofclasses, key=sort_key), sort_key)
-:: for (k, v) in msgtype_groups:
-:: k = util.constant_for_value(version, "ofp_action_type", k)
-:: v = list(v)
-:: if len(v) == 1:
-    ${k} : ${v[0].pyname}.unpack,
-:: else:
-    ${k} : parse_${k[12:].lower()},
-:: #endif
-:: #endfor
-}
-
-:: experimenter_ofclasses = [x for x in ofclasses if x.type_members[0].value == 0xffff]
-:: sort_key = lambda x: x.type_members[1].value
-:: experimenter_ofclasses.sort(key=sort_key)
-:: grouped = itertools.groupby(experimenter_ofclasses, sort_key)
-experimenter_parsers = {
-:: for (experimenter, v) in grouped:
-    ${experimenter} : {
-:: for ofclass in v:
-        ${ofclass.type_members[2].value}: ${ofclass.pyname}.unpack,
-:: #endfor
-    },
-:: #endfor
-}
diff --git a/py_gen/templates/common.py b/py_gen/templates/common.py
deleted file mode 100644
index 76ae631..0000000
--- a/py_gen/templates/common.py
+++ /dev/null
@@ -1,106 +0,0 @@
-:: # 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.
-::
-:: include('_copyright.py')
-:: from loxi_globals import OFVersions
-:: include('_autogen.py')
-
-import sys
-import struct
-import action
-:: if version >= OFVersions.VERSION_1_1:
-import instruction # for unpack_list
-:: #endif
-:: if version >= OFVersions.VERSION_1_3:
-import meter_band # for unpack_list
-:: #endif
-import const
-import util
-import loxi.generic_util
-
-:: if version >= OFVersions.VERSION_1_2:
-import oxm
-:: #endif
-
-# HACK make this module visible as 'common' to simplify code generation
-common = sys.modules[__name__]
-
-def unpack_list_flow_stats_entry(reader):
-    return loxi.generic_util.unpack_list_lv16(reader, flow_stats_entry.unpack)
-
-def unpack_list_queue_prop(reader):
-    def deserializer(reader, typ):
-        if typ == const.OFPQT_MIN_RATE:
-            return queue_prop_min_rate.unpack(reader)
-        else:
-            raise loxi.ProtocolError("unknown queue prop %d" % typ)
-    return loxi.generic_util.unpack_list_tlv16(reader, deserializer)
-
-def unpack_list_packet_queue(reader):
-    def wrapper(reader):
-        length, = reader.peek('!4xH')
-        return packet_queue.unpack(reader.slice(length))
-    return loxi.generic_util.unpack_list(reader, wrapper)
-
-def unpack_list_hello_elem(reader):
-    def deserializer(reader, typ):
-        if typ == const.OFPHET_VERSIONBITMAP:
-            return hello_elem_versionbitmap.unpack(reader)
-        else:
-            return None
-    return [x for x in loxi.generic_util.unpack_list_tlv16(reader, deserializer) if x != None]
-
-def unpack_list_bucket(reader):
-    return loxi.generic_util.unpack_list_lv16(reader, bucket.unpack)
-
-def unpack_list_group_desc_stats_entry(reader):
-    return loxi.generic_util.unpack_list_lv16(reader, group_desc_stats_entry.unpack)
-
-def unpack_list_group_stats_entry(reader):
-    return loxi.generic_util.unpack_list_lv16(reader, group_stats_entry.unpack)
-
-def unpack_list_meter_stats(reader):
-    def wrapper(reader):
-        length, = reader.peek('!4xH')
-        return meter_stats.unpack(reader.slice(length))
-    return loxi.generic_util.unpack_list(reader, wrapper)
-
-:: for ofclass in ofclasses:
-:: include('_ofclass.py', ofclass=ofclass, superclass="object")
-
-:: #endfor
-
-:: if version == OFVersions.VERSION_1_0:
-match = match_v1
-:: elif version == OFVersions.VERSION_1_1:
-match = match_v2
-:: elif version == OFVersions.VERSION_1_2:
-match = match_v3
-:: elif version == OFVersions.VERSION_1_3:
-:: # HACK
-match = match_v3
-:: #endif
diff --git a/py_gen/templates/generic_util.py b/py_gen/templates/generic_util.py
index 28d3b5f..039d82a 100644
--- a/py_gen/templates/generic_util.py
+++ b/py_gen/templates/generic_util.py
@@ -35,6 +35,9 @@
 import loxi
 import struct
 
+def pack_list(values):
+    return "".join([x.pack() for x in values])
+
 def unpack_list(reader, deserializer):
     """
     The deserializer function should take an OFReader and return the new object.
@@ -44,25 +47,6 @@
         entries.append(deserializer(reader))
     return entries
 
-def unpack_list_lv16(reader, deserializer):
-    """
-    The deserializer function should take an OFReader and return the new object.
-    """
-    def wrapper(reader):
-        length, = reader.peek('!H')
-        return deserializer(reader.slice(length))
-    return unpack_list(reader, wrapper)
-
-def unpack_list_tlv16(reader, deserializer):
-    """
-    The deserializer function should take an OFReader and an integer type
-    and return the new object.
-    """
-    def wrapper(reader):
-        typ, length, = reader.peek('!HH')
-        return deserializer(reader.slice(length), typ)
-    return unpack_list(reader, wrapper)
-
 def pad_to(alignment, length):
     """
     Return a string of zero bytes that will pad a string of length 'length' to
@@ -79,50 +63,60 @@
     known field lengths. This class supports efficiently reading
     fields sequentially and is intended to be used recursively by the
     parsers of child objects which will implicitly update the offset.
+
+    buf: buffer object
+    start: initial position in the buffer
+    length: number of bytes after start
+    offset: distance from start
     """
-    def __init__(self, buf):
+    def __init__(self, buf, start=0, length=None):
         self.buf = buf
+        self.start = start
+        if length is None:
+            self.length = len(buf) - start
+        else:
+            self.length = length
         self.offset = 0
 
     def read(self, fmt):
         st = struct.Struct(fmt)
-        if self.offset + st.size > len(self.buf):
+        if self.offset + st.size > self.length:
             raise loxi.ProtocolError("Buffer too short")
-        result = st.unpack_from(self.buf, self.offset)
+        result = st.unpack_from(self.buf, self.start+self.offset)
         self.offset += st.size
         return result
 
     def read_all(self):
-        buf = buffer(self.buf, self.offset)
-        self.offset += len(buf)
-        return str(buf)
+        s = self.buf[(self.start+self.offset):(self.start+self.length)]
+        assert(len(s) == self.length - self.offset)
+        self.offset = self.length
+        return s
 
-    def peek(self, fmt):
+    def peek(self, fmt, offset=0):
         st = struct.Struct(fmt)
-        if self.offset + st.size > len(self.buf):
+        if self.offset + offset + st.size > self.length:
             raise loxi.ProtocolError("Buffer too short")
-        result = st.unpack_from(self.buf, self.offset)
+        result = st.unpack_from(self.buf, self.start + self.offset + offset)
         return result
 
     def skip(self, length):
-        if self.offset + length > len(self.buf):
+        if self.offset + length > self.length:
             raise loxi.ProtocolError("Buffer too short")
         self.offset += length
 
     def skip_align(self):
-        new_offset = (self.offset + 7) / 8 * 8
-        if new_offset > len(self.buf):
+        new_offset = ((self.start + self.offset + 7) / 8 * 8) - self.start
+        if new_offset > self.length:
             raise loxi.ProtocolError("Buffer too short")
         self.offset = new_offset
 
     def is_empty(self):
-        return self.offset == len(self.buf)
+        return self.offset == self.length
 
-    # Used when parsing variable length objects which have external length
-    # fields (e.g. the actions list in an OF 1.0 packet-out message).
+    # Used when parsing objects that have their own length fields
     def slice(self, length):
-        if self.offset + length > len(self.buf):
+        if self.offset + length > self.length:
             raise loxi.ProtocolError("Buffer too short")
-        buf = OFReader(buffer(self.buf, self.offset, length))
+        reader = OFReader(self.buf, self.start + self.offset, length)
         self.offset += length
-        return buf
+        return reader
diff --git a/py_gen/templates/instruction.py b/py_gen/templates/instruction.py
deleted file mode 100644
index 2817625..0000000
--- a/py_gen/templates/instruction.py
+++ /dev/null
@@ -1,95 +0,0 @@
-:: # 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 itertools
-:: import py_gen.util as util
-:: include('_copyright.py')
-
-:: include('_autogen.py')
-
-import struct
-import action
-import const
-import util
-import loxi.generic_util
-import loxi
-
-def unpack_list(reader):
-    def deserializer(reader, typ):
-        parser = parsers.get(typ)
-        if not parser: raise loxi.ProtocolError("unknown instruction type %d" % typ)
-        return parser(reader)
-    return loxi.generic_util.unpack_list_tlv16(reader, deserializer)
-
-class Instruction(object):
-    type = None # override in subclass
-    pass
-
-:: for ofclass in ofclasses:
-:: include('_ofclass.py', ofclass=ofclass, superclass="Instruction")
-
-:: #endfor
-
-def parse_experimenter(reader):
-    experimenter, = reader.peek("!4xL")
-    if experimenter == 0x005c16c7: # Big Switch Networks
-        subtype, = reader.peek("!8xL")
-    else:
-        raise loxi.ProtocolError("unexpected experimenter id %#x" % experimenter)
-
-    if subtype in experimenter_parsers[experimenter]:
-        return experimenter_parsers[experimenter][subtype](reader)
-    else:
-        raise loxi.ProtocolError("unexpected experimenter id %#x subtype %#x" % (experimenter, subtype))
-
-parsers = {
-:: sort_key = lambda x: x.type_members[0].value
-:: msgtype_groups = itertools.groupby(sorted(ofclasses, key=sort_key), sort_key)
-:: for (k, v) in msgtype_groups:
-:: k = util.constant_for_value(version, "ofp_instruction_type", k)
-:: v = list(v)
-:: if len(v) == 1 and k != 'const.OFPIT_EXPERIMENTER':
-    ${k} : ${v[0].pyname}.unpack,
-:: else:
-    ${k} : parse_${k[12:].lower()},
-:: #endif
-:: #endfor
-}
-
-:: experimenter_ofclasses = [x for x in ofclasses if x.type_members[0].value == 0xffff]
-:: sort_key = lambda x: x.type_members[1].value
-:: experimenter_ofclasses.sort(key=sort_key)
-:: grouped = itertools.groupby(experimenter_ofclasses, sort_key)
-experimenter_parsers = {
-:: for (experimenter, v) in grouped:
-    ${experimenter} : {
-:: for ofclass in v:
-        ${ofclass.type_members[2].value}: ${ofclass.pyname}.unpack,
-:: #endfor
-    },
-:: #endfor
-}
diff --git a/py_gen/templates/message.py b/py_gen/templates/message.py
deleted file mode 100644
index 39e9722..0000000
--- a/py_gen/templates/message.py
+++ /dev/null
@@ -1,351 +0,0 @@
-:: # 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 itertools
-:: from loxi_globals import OFVersions
-:: import loxi_globals
-:: import py_gen.util as util
-:: import py_gen.oftype
-:: include('_copyright.py')
-
-:: include('_autogen.py')
-
-import struct
-import loxi
-import const
-import common
-import action # for unpack_list
-:: if version >= OFVersions.VERSION_1_1:
-import instruction # for unpack_list
-:: #endif
-:: if version >= OFVersions.VERSION_1_3:
-import meter_band # for unpack_list
-:: #endif
-import util
-import loxi.generic_util
-
-class Message(object):
-    version = const.OFP_VERSION
-    type = None # override in subclass
-    xid = None
-
-:: for ofclass in ofclasses:
-:: from loxi_ir import *
-:: normal_members = [m for m in ofclass.members if type(m) == OFDataMember]
-:: type_members = [m for m in ofclass.members if type(m) == OFTypeMember]
-class ${ofclass.pyname}(Message):
-:: for m in type_members:
-    ${m.name} = ${m.value}
-:: #endfor
-
-    def __init__(self, ${', '.join(["%s=None" % m.name for m in normal_members])}):
-        self.xid = xid
-:: for m in [x for x in normal_members if x.name != 'xid']:
-        if ${m.name} != None:
-            self.${m.name} = ${m.name}
-        else:
-            self.${m.name} = ${py_gen.oftype.gen_init_expr(m.oftype, version=version)}
-:: #endfor
-
-    def pack(self):
-        packed = []
-:: include('_pack.py', ofclass=ofclass, version=version)
-        return ''.join(packed)
-
-    @staticmethod
-    def unpack(buf):
-        if len(buf) < 8: raise loxi.ProtocolError("buffer too short to contain an OpenFlow message")
-        obj = ${ofclass.pyname}()
-:: include('_unpack.py', ofclass=ofclass, version=version)
-        return obj
-
-    def __eq__(self, other):
-        if type(self) != type(other): return False
-        if self.version != other.version: return False
-        if self.type != other.type: return False
-:: for m in normal_members:
-        if self.${m.name} != other.${m.name}: return False
-:: #endfor
-        return True
-
-    def __ne__(self, other):
-        return not self.__eq__(other)
-
-    def __str__(self):
-        return self.show()
-
-    def show(self):
-        import loxi.pp
-        return loxi.pp.pp(self)
-
-    def pretty_print(self, q):
-:: include('_pretty_print.py', ofclass=ofclass)
-
-:: #endfor
-
-def parse_header(buf):
-    if len(buf) < 8:
-        raise loxi.ProtocolError("too short to be an OpenFlow message")
-    return struct.unpack_from("!BBHL", buf)
-
-def parse_message(buf):
-    msg_ver, msg_type, msg_len, msg_xid = parse_header(buf)
-    if msg_ver != const.OFP_VERSION and msg_type != const.OFPT_HELLO:
-        raise loxi.ProtocolError("wrong OpenFlow version (expected %d, got %d)" % (const.OFP_VERSION, msg_ver))
-    if len(buf) != msg_len:
-        raise loxi.ProtocolError("incorrect message size")
-    if msg_type in parsers:
-        return parsers[msg_type](buf)
-    else:
-        raise loxi.ProtocolError("unexpected message type")
-
-def parse_error(buf):
-    if len(buf) < 8 + 2:
-        raise loxi.ProtocolError("message too short")
-    err_type, = struct.unpack_from("!H", buf, 8)
-    if err_type in error_msg_parsers:
-        return error_msg_parsers[err_type](buf)
-    else:
-        raise loxi.ProtocolError("unexpected error type %u" % err_type)
-
-def parse_flow_mod(buf):
-:: if version == OFVersions.VERSION_1_0:
-:: offset = 57
-:: elif version >= OFVersions.VERSION_1_1:
-:: offset = 25
-:: #endif
-    if len(buf) < ${offset} + 1:
-        raise loxi.ProtocolError("message too short")
-    # Technically uint16_t for OF 1.0
-    cmd, = struct.unpack_from("!B", buf, ${offset})
-    if cmd in flow_mod_parsers:
-        return flow_mod_parsers[cmd](buf)
-    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")
-    stats_type, = struct.unpack_from("!H", buf, 8)
-    if stats_type in stats_reply_parsers:
-        return stats_reply_parsers[stats_type](buf)
-    else:
-        raise loxi.ProtocolError("unexpected stats type %u" % stats_type)
-
-def parse_stats_request(buf):
-    if len(buf) < 8 + 2:
-        raise loxi.ProtocolError("message too short")
-    stats_type, = struct.unpack_from("!H", buf, 8)
-    if stats_type in stats_request_parsers:
-        return stats_request_parsers[stats_type](buf)
-    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")
-
-    experimenter, = struct.unpack_from("!L", buf, 8)
-    if experimenter == 0x005c16c7: # Big Switch Networks
-        subtype, = struct.unpack_from("!L", buf, 12)
-    elif experimenter == 0x00002320: # Nicira
-        subtype, = struct.unpack_from("!L", buf, 12)
-    else:
-        raise loxi.ProtocolError("unexpected experimenter id %#x" % experimenter)
-
-    if subtype in experimenter_parsers[experimenter]:
-        return experimenter_parsers[experimenter][subtype](buf)
-    else:
-        raise loxi.ProtocolError("unexpected experimenter %#x subtype %#x" % (experimenter, subtype))
-
-parsers = {
-:: sort_key = lambda x: x.type_members[1].value
-:: msgtype_groups = itertools.groupby(sorted(ofclasses, key=sort_key), sort_key)
-:: for (k, v) in msgtype_groups:
-:: k = util.constant_for_value(version, "ofp_type", k)
-:: v = list(v)
-:: if len(v) == 1:
-    ${k} : ${v[0].pyname}.unpack,
-:: else:
-    ${k} : parse_${k[11:].lower()},
-:: #endif
-:: #endfor
-}
-
-error_msg_parsers = {
-    const.OFPET_HELLO_FAILED : hello_failed_error_msg.unpack,
-    const.OFPET_BAD_REQUEST : bad_request_error_msg.unpack,
-    const.OFPET_BAD_ACTION : bad_action_error_msg.unpack,
-    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 >= 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 >= 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 >= 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
-}
-
-flow_mod_parsers = {
-    const.OFPFC_ADD : flow_add.unpack,
-    const.OFPFC_MODIFY : flow_modify.unpack,
-    const.OFPFC_MODIFY_STRICT : flow_modify_strict.unpack,
-    const.OFPFC_DELETE : flow_delete.unpack,
-    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,
-    const.OFPST_AGGREGATE : aggregate_stats_reply.unpack,
-    const.OFPST_TABLE : table_stats_reply.unpack,
-    const.OFPST_PORT : port_stats_reply.unpack,
-    const.OFPST_QUEUE : queue_stats_reply.unpack,
-    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 >= OFVersions.VERSION_1_2:
-    const.OFPST_GROUP_FEATURES : group_features_stats_reply.unpack,
-:: #endif
-:: 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,
-    const.OFPST_TABLE_FEATURES : table_features_stats_reply.unpack,
-    const.OFPST_PORT_DESC : port_desc_stats_reply.unpack,
-:: #endif
-}
-
-stats_request_parsers = {
-    const.OFPST_DESC : desc_stats_request.unpack,
-    const.OFPST_FLOW : flow_stats_request.unpack,
-    const.OFPST_AGGREGATE : aggregate_stats_request.unpack,
-    const.OFPST_TABLE : table_stats_request.unpack,
-    const.OFPST_PORT : port_stats_request.unpack,
-    const.OFPST_QUEUE : queue_stats_request.unpack,
-    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 >= OFVersions.VERSION_1_2:
-    const.OFPST_GROUP_FEATURES : group_features_stats_request.unpack,
-:: #endif
-:: 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,
-    const.OFPST_TABLE_FEATURES : table_features_stats_request.unpack,
-    const.OFPST_PORT_DESC : port_desc_stats_request.unpack,
-:: #endif
-}
-
-:: experimenter_ofclasses = [x for x in ofclasses if x.type_members[1].value == 4]
-:: sort_key = lambda x: x.type_members[2].value
-:: experimenter_ofclasses.sort(key=sort_key)
-:: grouped = itertools.groupby(experimenter_ofclasses, sort_key)
-experimenter_parsers = {
-:: for (experimenter, v) in grouped:
-    ${experimenter} : {
-:: for ofclass in v:
-        ${ofclass.type_members[3].value}: ${ofclass.pyname}.unpack,
-:: #endfor
-    },
-:: #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/module.py
similarity index 64%
rename from py_gen/templates/meter_band.py
rename to py_gen/templates/module.py
index 37cc6d9..252bf0d 100644
--- a/py_gen/templates/meter_band.py
+++ b/py_gen/templates/module.py
@@ -25,44 +25,38 @@
 :: # EPL for the specific language governing permissions and limitations
 :: # under the EPL.
 ::
-:: import itertools
-:: import py_gen.util as util
+:: from loxi_globals import OFVersions
+:: import py_gen.oftype
 :: include('_copyright.py')
 
 :: include('_autogen.py')
 
 import struct
+import loxi
 import const
+import common
+import action
+:: if version >= OFVersions.VERSION_1_1:
+import instruction
+:: #endif
+:: if version >= OFVersions.VERSION_1_2:
+import oxm
+:: #endif
+:: if version >= OFVersions.VERSION_1_3:
+import meter_band
+:: #endif
 import util
 import loxi.generic_util
-import loxi
-
-def unpack_list(reader):
-    def deserializer(reader, typ):
-        parser = parsers.get(typ)
-        if not parser: raise loxi.ProtocolError("unknown meter band type %d" % typ)
-        return parser(reader)
-    return loxi.generic_util.unpack_list_tlv16(reader, deserializer)
-
-class MeterBand(object):
-    type = None # override in subclass
-    pass
 
 :: for ofclass in ofclasses:
-:: include('_ofclass.py', ofclass=ofclass, superclass="MeterBand")
-
-:: #endfor
-
-parsers = {
-:: sort_key = lambda x: x.type_members[0].value
-:: msgtype_groups = itertools.groupby(sorted(ofclasses, key=sort_key), sort_key)
-:: for (k, v) in msgtype_groups:
-:: k = util.constant_for_value(version, "ofp_meter_band_type", k)
-:: v = list(v)
-:: if len(v) == 1:
-    ${k} : ${v[0].pyname}.unpack,
+:: if ofclass.virtual:
+:: include('_virtual_ofclass.py', ofclass=ofclass)
 :: else:
-    ${k} : parse_${k[12:].lower()},
+:: include('_ofclass.py', ofclass=ofclass)
 :: #endif
+
 :: #endfor
-}
+
+:: if 'extra_template' in locals():
+:: include(extra_template)
+:: #endif
diff --git a/py_gen/templates/oxm.py b/py_gen/templates/oxm.py
deleted file mode 100644
index ecb52ac..0000000
--- a/py_gen/templates/oxm.py
+++ /dev/null
@@ -1,107 +0,0 @@
-:: # 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 itertools
-:: import py_gen.oftype
-:: include('_copyright.py')
-
-:: include('_autogen.py')
-
-import struct
-import const
-import util
-import loxi.generic_util
-import loxi
-
-def unpack(reader):
-    type_len, = reader.peek('!L')
-    if type_len in parsers:
-        return parsers[type_len](reader)
-    else:
-        raise loxi.ProtocolError("unknown OXM cls=%#x type=%#x masked=%d len=%d (%#x)" % \
-            ((type_len >> 16) & 0xffff, (type_len >> 9) & 0x7f, (type_len >> 8) & 1, type_len & 0xff, type_len))
-
-def unpack_list(reader):
-    return loxi.generic_util.unpack_list(reader, unpack)
-
-class OXM(object):
-    type_len = None # override in subclass
-    pass
-
-:: for ofclass in ofclasses:
-:: from loxi_ir import *
-:: normal_members = [m for m in ofclass.members if type(m) == OFDataMember]
-:: type_members = [m for m in ofclass.members if type(m) == OFTypeMember]
-class ${ofclass.pyname}(OXM):
-:: for m in type_members:
-    ${m.name} = ${m.value}
-:: #endfor
-
-    def __init__(self, ${', '.join(["%s=None" % m.name for m in normal_members])}):
-:: for m in normal_members:
-        if ${m.name} != None:
-            self.${m.name} = ${m.name}
-        else:
-            self.${m.name} = ${py_gen.oftype.gen_init_expr(m.oftype, version=version)}
-:: #endfor
-
-    def pack(self):
-        packed = []
-:: include("_pack.py", ofclass=ofclass, version=version)
-        return ''.join(packed)
-
-    @staticmethod
-    def unpack(buf):
-        obj = ${ofclass.pyname}()
-:: include("_unpack.py", ofclass=ofclass, version=version)
-        return obj
-
-    def __eq__(self, other):
-        if type(self) != type(other): return False
-:: for m in normal_members:
-        if self.${m.name} != other.${m.name}: return False
-:: #endfor
-        return True
-
-    def __ne__(self, other):
-        return not self.__eq__(other)
-
-    def show(self):
-        import loxi.pp
-        return loxi.pp.pp(self)
-
-    def pretty_print(self, q):
-:: include('_pretty_print.py', ofclass=ofclass)
-
-:: #endfor
-
-parsers = {
-:: key = lambda x: x.type_members[0].value
-:: for ofclass in sorted(ofclasses, key=key):
-    ${key(ofclass)} : ${ofclass.pyname}.unpack,
-:: #endfor
-}
diff --git a/py_gen/templates/toplevel_init.py b/py_gen/templates/toplevel_init.py
index b170bd8..e5493a5 100644
--- a/py_gen/templates/toplevel_init.py
+++ b/py_gen/templates/toplevel_init.py
@@ -68,3 +68,17 @@
 
 def unimplemented(msg):
     raise Unimplemented(msg)
+
+class OFObject(object):
+    """
+    Superclass of all OpenFlow classes
+    """
+    def __init__(self, *args):
+        raise NotImplementedError("cannot instantiate abstract class")
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def show(self):
+        import loxi.pp
+        return loxi.pp.pp(self)
diff --git a/py_gen/templates/util.py b/py_gen/templates/util.py
index 50a64db..8ad246a 100644
--- a/py_gen/templates/util.py
+++ b/py_gen/templates/util.py
@@ -29,9 +29,20 @@
 :: from loxi_globals import OFVersions
 :: include('_autogen.py')
 
+import struct
 import loxi
 import const
-import struct
+import common
+import action
+:: if version >= OFVersions.VERSION_1_1:
+import instruction
+:: #endif
+:: if version >= OFVersions.VERSION_1_2:
+import oxm
+:: #endif
+:: if version >= OFVersions.VERSION_1_3:
+import meter_band
+:: #endif
 
 def pretty_mac(mac):
     return ':'.join(["%02x" % x for x in mac])
@@ -140,9 +151,6 @@
     return reader.read("!Q")[0]
 :: #endif
 
-def pack_list(values):
-    return "".join([x.pack() for x in values])
-
 MASK64 = (1 << 64) - 1
 
 def pack_bitmap_128(value):
@@ -162,3 +170,13 @@
         i += 1
         x >>= 1
     return value
+
+def unpack_list_hello_elem(reader):
+    def deserializer(reader):
+        typ, length, = reader.peek('!HH')
+        reader = reader.slice(length)
+        try:
+            return common.hello_elem.unpack(reader)
+        except loxi.ProtocolError:
+            return None
+    return [x for x in loxi.generic_util.unpack_list(reader, deserializer) if x != None]
diff --git a/py_gen/tests/generic_util.py b/py_gen/tests/generic_util.py
index da045b9..8b2f59f 100644
--- a/py_gen/tests/generic_util.py
+++ b/py_gen/tests/generic_util.py
@@ -43,15 +43,6 @@
         a = loxi.generic_util.unpack_list(reader, deserializer)
         self.assertEquals(['\x04abc', '\x03de', '\x02f', '\x01'], a)
 
-class TestUnpackListLV16(unittest.TestCase):
-    def test_simple(self):
-        def deserializer(reader):
-            reader.skip(2)
-            return reader.read_all()
-        reader = loxi.generic_util.OFReader("\x00\x05abc\x00\x04de\x00\x03f\x00\x02")
-        a = loxi.generic_util.unpack_list_lv16(reader, deserializer)
-        self.assertEquals(['abc', 'de', 'f', ''], a)
-
 class TestOFReader(unittest.TestCase):
     def test_empty(self):
         reader = OFReader("")
@@ -116,5 +107,18 @@
         self.assertEquals(reader.slice(2).read_all(), "fg")
         self.assertEquals(reader.is_empty(), True)
 
+    def test_skip_align(self):
+        reader = OFReader("abcd" + "efgh" + "ijkl" + "mnop" + "qr")
+        reader.skip_align()
+        self.assertEquals(reader.peek('2s')[0], 'ab')
+        self.assertEquals(reader.read('2s')[0], "ab")
+        reader.skip_align()
+        self.assertEquals(reader.peek('2s')[0], 'ij')
+        self.assertEquals(reader.read('2s')[0], 'ij')
+        child = reader.slice(8)
+        self.assertEquals(child.peek('2s')[0], 'kl')
+        child.skip_align()
+        self.assertEquals(child.peek('2s')[0], 'qr')
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/py_gen/tests/of10.py b/py_gen/tests/of10.py
index be93239..250000f 100644
--- a/py_gen/tests/of10.py
+++ b/py_gen/tests/of10.py
@@ -30,6 +30,7 @@
 from testutil import add_datafiles_tests
 
 try:
+    import loxi
     import loxi.of10 as ofp
     from loxi.generic_util import OFReader
 except ImportError:
@@ -87,34 +88,34 @@
         add(ofp.action.bsn_set_tunnel_dst(dst=0x12345678))
         add(ofp.action.nicira_dec_ttl())
 
-        actions = ofp.action.unpack_list(OFReader(''.join(bufs)))
+        actions = loxi.generic_util.unpack_list(OFReader(''.join(bufs)), ofp.action.action.unpack)
         self.assertEquals(actions, expected)
 
     def test_empty_list(self):
-        self.assertEquals(ofp.action.unpack_list(OFReader('')), [])
+        self.assertEquals(loxi.generic_util.unpack_list(OFReader(''), ofp.action.action.unpack), [])
 
     def test_invalid_list_length(self):
         buf = '\x00' * 9
         with self.assertRaisesRegexp(ofp.ProtocolError, 'Buffer too short'):
-            ofp.action.unpack_list(OFReader(buf))
+            loxi.generic_util.unpack_list(OFReader(buf), ofp.action.action.unpack)
 
     def test_invalid_action_length(self):
         buf = '\x00' * 8
         with self.assertRaisesRegexp(ofp.ProtocolError, 'Buffer too short'):
-            ofp.action.unpack_list(OFReader(buf))
+            loxi.generic_util.unpack_list(OFReader(buf), ofp.action.action.unpack)
 
         buf = '\x00\x00\x00\x04'
         with self.assertRaisesRegexp(ofp.ProtocolError, 'Buffer too short'):
-            ofp.action.unpack_list(OFReader(buf))
+            loxi.generic_util.unpack_list(OFReader(buf), ofp.action.action.unpack)
 
         buf = '\x00\x00\x00\x10\x00\x00\x00\x00'
         with self.assertRaisesRegexp(ofp.ProtocolError, 'Buffer too short'):
-            ofp.action.unpack_list(OFReader(buf))
+            loxi.generic_util.unpack_list(OFReader(buf), ofp.action.action.unpack)
 
     def test_invalid_action_type(self):
         buf = '\xff\xfe\x00\x08\x00\x00\x00\x00'
-        with self.assertRaisesRegexp(ofp.ProtocolError, 'unknown action type'):
-            ofp.action.unpack_list(OFReader(buf))
+        with self.assertRaisesRegexp(ofp.ProtocolError, 'unknown action subtype'):
+            loxi.generic_util.unpack_list(OFReader(buf), ofp.action.action.unpack)
 
 class TestConstants(unittest.TestCase):
     def test_ports(self):
@@ -129,7 +130,7 @@
         self.assertEquals(match.wildcards, ofp.OFPFW_ALL)
         self.assertEquals(match.tcp_src, 0)
         buf = match.pack()
-        match2 = ofp.match.unpack(buf)
+        match2 = ofp.match.unpack(OFReader(buf))
         self.assertEquals(match, match2)
 
 class TestMessages(unittest.TestCase):
@@ -152,8 +153,8 @@
 
     def test_echo_request_invalid_length(self):
         buf = "\x01\x02\x00\x07\x12\x34\x56"
-        with self.assertRaisesRegexp(ofp.ProtocolError, "buffer too short"):
-            ofp.message.echo_request.unpack(buf)
+        with self.assertRaisesRegexp(ofp.ProtocolError, "Buffer too short"):
+            ofp.message.echo_request.unpack(OFReader(buf))
 
     def test_echo_request_equality(self):
         msg = ofp.message.echo_request(xid=0x12345678, data="abc")
@@ -195,11 +196,11 @@
         msg = ofp.message.parse_message(buf)
         assert(msg.xid == 0x12345678)
 
-        # Get a list of all message classes
+        # Get a list of all concrete message classes
         test_klasses = [x for x in ofp.message.__dict__.values()
                         if type(x) == type
-                           and issubclass(x, ofp.message.Message)
-                           and x != ofp.message.Message]
+                           and issubclass(x, ofp.message.message)
+                           and hasattr(x, 'pack')]
 
         for klass in test_klasses:
             self.assertIsInstance(ofp.message.parse_message(klass(xid=1).pack()), klass)
@@ -228,7 +229,9 @@
         mods = [ofp.action,ofp.message,ofp.common]
         self.klasses = [klass for mod in mods
                               for klass in mod.__dict__.values()
-                              if hasattr(klass, 'show')]
+                              if isinstance(klass, type) and
+                                 issubclass(klass, loxi.OFObject) and
+                                 hasattr(klass, 'pack')]
         self.klasses.sort(key=lambda x: str(x))
 
     def test_serialization(self):
@@ -238,7 +241,7 @@
                 obj = klass()
                 if hasattr(obj, "xid"): obj.xid = 42
                 buf = obj.pack()
-                obj2 = klass.unpack(buf)
+                obj2 = klass.unpack(OFReader(buf))
                 self.assertEquals(obj, obj2)
             if klass in expected_failures:
                 self.assertRaises(Exception, fn)
@@ -248,7 +251,7 @@
     def test_parse_message(self):
         expected_failures = []
         for klass in self.klasses:
-            if not issubclass(klass, ofp.message.Message):
+            if not issubclass(klass, ofp.message.message):
                 continue
             def fn():
                 obj = klass(xid=42)
diff --git a/py_gen/tests/of11.py b/py_gen/tests/of11.py
index 07a5437..d620509 100644
--- a/py_gen/tests/of11.py
+++ b/py_gen/tests/of11.py
@@ -28,7 +28,9 @@
 import unittest
 
 try:
+    import loxi
     import loxi.of11 as ofp
+    from loxi.generic_util import OFReader
 except ImportError:
     exit("loxi package not found. Try setting PYTHONPATH.")
 
@@ -65,7 +67,9 @@
         mods = [ofp.action,ofp.message,ofp.common]
         self.klasses = [klass for mod in mods
                               for klass in mod.__dict__.values()
-                              if hasattr(klass, 'show')]
+                              if isinstance(klass, type) and
+                                 issubclass(klass, loxi.OFObject) and
+                                 hasattr(klass, 'pack')]
         self.klasses.sort(key=lambda x: str(x))
 
     def test_serialization(self):
@@ -75,7 +79,7 @@
                 obj = klass()
                 if hasattr(obj, "xid"): obj.xid = 42
                 buf = obj.pack()
-                obj2 = klass.unpack(buf)
+                obj2 = klass.unpack(OFReader(buf))
                 self.assertEquals(obj, obj2)
             if klass in expected_failures:
                 self.assertRaises(Exception, fn)
@@ -85,7 +89,7 @@
     def test_parse_message(self):
         expected_failures = []
         for klass in self.klasses:
-            if not issubclass(klass, ofp.message.Message):
+            if not issubclass(klass, ofp.message.message):
                 continue
             def fn():
                 obj = klass(xid=42)
diff --git a/py_gen/tests/of12.py b/py_gen/tests/of12.py
index 98d999b..4774672 100644
--- a/py_gen/tests/of12.py
+++ b/py_gen/tests/of12.py
@@ -29,7 +29,9 @@
 from testutil import add_datafiles_tests
 
 try:
+    import loxi
     import loxi.of12 as ofp
+    from loxi.generic_util import OFReader
 except ImportError:
     exit("loxi package not found. Try setting PYTHONPATH.")
 
@@ -74,7 +76,9 @@
         mods = [ofp.action,ofp.message,ofp.common,ofp.oxm]
         self.klasses = [klass for mod in mods
                               for klass in mod.__dict__.values()
-                              if hasattr(klass, 'show')]
+                              if isinstance(klass, type) and
+                                 issubclass(klass, loxi.OFObject) and
+                                 hasattr(klass, 'pack')]
         self.klasses.sort(key=lambda x: str(x))
 
     def test_serialization(self):
@@ -86,7 +90,7 @@
                 obj = klass()
                 if hasattr(obj, "xid"): obj.xid = 42
                 buf = obj.pack()
-                obj2 = klass.unpack(buf)
+                obj2 = klass.unpack(OFReader(buf))
                 self.assertEquals(obj, obj2)
             if klass in expected_failures:
                 self.assertRaises(Exception, fn)
@@ -96,7 +100,7 @@
     def test_parse_message(self):
         expected_failures = []
         for klass in self.klasses:
-            if not issubclass(klass, ofp.message.Message):
+            if not issubclass(klass, ofp.message.message):
                 continue
             def fn():
                 obj = klass(xid=42)
diff --git a/py_gen/tests/of13.py b/py_gen/tests/of13.py
index 8c18f41..8e258df 100644
--- a/py_gen/tests/of13.py
+++ b/py_gen/tests/of13.py
@@ -30,6 +30,7 @@
 from testutil import add_datafiles_tests
 
 try:
+    import loxi
     import loxi.of13 as ofp
     from loxi.generic_util import OFReader
 except ImportError:
@@ -66,7 +67,7 @@
             '\x00\x00\x00\x04', # unknown type
             '\x00\x01\x00\x04', # versionbitmap
         ])
-        l = ofp.unpack_list_hello_elem(OFReader(buf))
+        l = ofp.util.unpack_list_hello_elem(OFReader(buf))
         self.assertEquals(len(l), 2)
         self.assertTrue(isinstance(l[0], ofp.hello_elem_versionbitmap))
         self.assertTrue(isinstance(l[1], ofp.hello_elem_versionbitmap))
@@ -88,26 +89,21 @@
         mods = [ofp.action,ofp.message,ofp.common,ofp.oxm]
         self.klasses = [klass for mod in mods
                               for klass in mod.__dict__.values()
-                              if hasattr(klass, 'show')]
+                              if isinstance(klass, type) and
+                                 issubclass(klass, loxi.OFObject) and
+                                 hasattr(klass, 'pack')]
         self.klasses.sort(key=lambda x: str(x))
 
     def test_serialization(self):
         expected_failures = [
             ofp.action.set_field, # field defaults to None
-            ofp.common.table_feature_prop_apply_actions,
-            ofp.common.table_feature_prop_apply_actions_miss,
-            ofp.common.table_feature_prop_write_actions,
-            ofp.common.table_feature_prop_write_actions_miss,
-            ofp.common.table_features,
-            ofp.message.table_features_stats_reply,
-            ofp.message.table_features_stats_request,
         ]
         for klass in self.klasses:
             def fn():
                 obj = klass()
                 if hasattr(obj, "xid"): obj.xid = 42
                 buf = obj.pack()
-                obj2 = klass.unpack(buf)
+                obj2 = klass.unpack(OFReader(buf))
                 self.assertEquals(obj, obj2)
             if klass in expected_failures:
                 self.assertRaises(Exception, fn)
@@ -116,11 +112,9 @@
 
     def test_parse_message(self):
         expected_failures = [
-            ofp.message.table_features_stats_reply,
-            ofp.message.table_features_stats_request,
         ]
         for klass in self.klasses:
-            if not issubclass(klass, ofp.message.Message):
+            if not issubclass(klass, ofp.message.message):
                 continue
             def fn():
                 obj = klass(xid=42)
diff --git a/py_gen/tests/testutil.py b/py_gen/tests/testutil.py
index ea30902..f8d5543 100644
--- a/py_gen/tests/testutil.py
+++ b/py_gen/tests/testutil.py
@@ -32,6 +32,7 @@
 import os
 import unittest
 import test_data
+from loxi.generic_util import OFReader
 
 # Human-friendly format for binary strings. 8 bytes per line.
 def format_binary(buf):
@@ -55,7 +56,7 @@
         b = format_binary(packed)
         raise AssertionError("Serialization of %s failed\nExpected:\n%s\nActual:\n%s\nDiff:\n%s" % \
             (type(obj).__name__, a, b, diff(a, b)))
-    unpacked = type(obj).unpack(buf)
+    unpacked = type(obj).unpack(OFReader(buf))
     if obj != unpacked:
         a = obj.show()
         b = unpacked.show()
diff --git a/py_gen/util.py b/py_gen/util.py
index b405441..2a0dc54 100644
--- a/py_gen/util.py
+++ b/py_gen/util.py
@@ -49,3 +49,10 @@
         if value == value2:
             return "const." + name
     return repr(value)
+
+def ancestors(ofclass):
+    r = []
+    while ofclass:
+        r.append(ofclass)
+        ofclass = ofclass.superclass
+    return r