Merge pull request #1 from floodlight/master

Syncing with floodligh/loxigen
diff --git a/c_gen/c_code_gen.py b/c_gen/c_code_gen.py
index 0a10715..09c4f2f 100644
--- a/c_gen/c_code_gen.py
+++ b/c_gen/c_code_gen.py
@@ -756,6 +756,19 @@
  *
  ****************************************************************/
 
+#ifdef __GNUC__
+#include <features.h>
+
+#if __GNUC_PREREQ(4,4)
+#pragma GCC optimize ("s")
+#endif
+
+#if __GNUC_PREREQ(4,6)
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
+#endif
+
+#endif
+
 #include <loci/loci.h>
 #include <loci/of_object.h>
 #include "loci_log.h"
@@ -892,13 +905,17 @@
     "init", \\
     "unknown"
 
-extern const char *of_error_strings[];
+extern const char *const of_error_strings[];
 
+#ifndef NDEBUG
 /* #define ASSERT(val) assert(val) */
 #define FORCE_FAULT *(volatile int *)0 = 1
 #define ASSERT(val) if (!(val)) \\
     fprintf(stderr, "\\nASSERT %s. %s:%d\\n", #val, __FILE__, __LINE__), \\
     FORCE_FAULT
+#else
+#define ASSERT(val)
+#endif
 
 /*
  * Some LOCI object accessors can fail, and it's easy to forget to check.
@@ -1101,8 +1118,10 @@
 static inline void
 of_object_parent_length_update(of_object_t *obj, int delta)
 {
+#ifndef NDEBUG
     int count = 0;
     of_wire_buffer_t *wbuf;  /* For debug asserts only */
+#endif
 
     while (obj != NULL) {
         ASSERT(count++ < _MAX_PARENT_ITERATIONS);
@@ -1110,7 +1129,9 @@
         if (obj->wire_length_set != NULL) {
             obj->wire_length_set(obj, obj->length);
         }
+#ifndef NDEBUG
         wbuf = obj->wire_object.wbuf;
+#endif
 
         /* Asserts for wire length checking */
         ASSERT(obj->length + obj->wire_object.obj_offset <=
@@ -1221,7 +1242,7 @@
     OF_OBJECT_COUNT = %d
 } of_object_id_t;
 
-extern const char *of_object_id_str[];
+extern const char *const of_object_id_str[];
 
 #define OF_MESSAGE_OBJECT_COUNT %d
 """ % ((last + 1), msg_count))
@@ -1282,7 +1303,7 @@
 """)
 
 def gen_object_enum_str(out):
-    out.write("\nconst char *of_object_id_str[] = {\n")
+    out.write("\nconst char *const of_object_id_str[] = {\n")
     out.write("    \"of_object\",\n")
     for cls in of_g.ordered_messages:
         out.write("    \"%s\",\n" % cls)
@@ -1299,7 +1320,7 @@
 
     # We'll do version strings while we're at it
     out.write("""
- const char *of_version_str[] = {
+ const char *const of_version_str[] = {
     "Unknown OpenFlow Version",
     "OpenFlow-1.0",
     "OpenFlow-1.1",
@@ -1335,7 +1356,7 @@
 /** @var of_error_strings
  * The error string map; use abs value to index
  */
-const char *of_error_strings[] = { OF_ERROR_STRINGS };
+const char *const of_error_strings[] = { OF_ERROR_STRINGS };
 """)
 
 ################################################################
@@ -2984,7 +3005,7 @@
     out.write("""
 typedef void (*of_object_init_f)(of_object_t *obj, of_version_t version,
     int bytes, int clean_wire);
-extern of_object_init_f of_object_init_map[];
+extern const of_object_init_f of_object_init_map[];
 """)
 
     out.write("""
@@ -3112,7 +3133,7 @@
 /**
  * Map from object ID to type coerce function
  */
-of_object_init_f of_object_init_map[] = {
+const of_object_init_f of_object_init_map[] = {
     (of_object_init_f)NULL,
 """)
     count = 1
diff --git a/c_gen/c_dump_gen.py b/c_gen/c_dump_gen.py
index dbf1e7a..08abae0 100644
--- a/c_gen/c_dump_gen.py
+++ b/c_gen/c_dump_gen.py
@@ -228,7 +228,7 @@
     # Generate big table indexed by version and object
     for version in of_g.of_version_range:
         out.write("""
-static loci_obj_dump_f dump_funs_v%(version)s[OF_OBJECT_COUNT] = {
+static const loci_obj_dump_f dump_funs_v%(version)s[OF_OBJECT_COUNT] = {
 """ % dict(version=version))
         out.write("    unknown_dump, /* of_object, not a valid specific type */\n")
         for j, cls in enumerate(of_g.all_class_order):
@@ -245,7 +245,7 @@
         out.write("};\n\n")
 
     out.write("""
-static loci_obj_dump_f *dump_funs[5] = {
+static const loci_obj_dump_f *const dump_funs[5] = {
     NULL,
     dump_funs_v1,
     dump_funs_v2,
diff --git a/c_gen/c_match.py b/c_gen/c_match.py
index 1b5be1e..8c27bb5 100644
--- a/c_gen/c_match.py
+++ b/c_gen/c_match.py
@@ -338,7 +338,7 @@
     out.write(""")
 
 /* Indexed by version number */
-extern uint64_t of_match_incompat[4];
+extern const uint64_t of_match_incompat[4];
 """)
 
 
@@ -387,7 +387,7 @@
 }
 
 /* Indexed by version number */
-uint64_t of_match_incompat[4] = {
+const uint64_t of_match_incompat[4] = {
     -1,
     OF_MATCH_V1_INCOMPAT,
     OF_MATCH_V2_INCOMPAT,
diff --git a/c_gen/c_show_gen.py b/c_gen/c_show_gen.py
index 5dab038..14709ab 100644
--- a/c_gen/c_show_gen.py
+++ b/c_gen/c_show_gen.py
@@ -226,7 +226,7 @@
     # Generate big table indexed by version and object
     for version in of_g.of_version_range:
         out.write("""
-static loci_obj_show_f show_funs_v%(version)s[OF_OBJECT_COUNT] = {
+static const loci_obj_show_f show_funs_v%(version)s[OF_OBJECT_COUNT] = {
 """ % dict(version=version))
         out.write("    unknown_show, /* of_object, not a valid specific type */\n")
         for j, cls in enumerate(of_g.all_class_order):
@@ -243,7 +243,7 @@
         out.write("};\n\n")
 
     out.write("""
-static loci_obj_show_f *show_funs[5] = {
+static const loci_obj_show_f *const show_funs[5] = {
     NULL,
     show_funs_v1,
     show_funs_v2,
diff --git a/c_gen/c_type_maps.py b/c_gen/c_type_maps.py
index 924243a..d790c85 100644
--- a/c_gen/c_type_maps.py
+++ b/c_gen/c_type_maps.py
@@ -52,7 +52,7 @@
  */
 """)
     for version in of_g.of_version_range:
-        out.write("static int\nof_object_to_type_map_v%d[OF_OBJECT_COUNT] = {\n"
+        out.write("static const int\nof_object_to_type_map_v%d[OF_OBJECT_COUNT] = {\n"
                   %version)
         out.write("    -1, /* of_object, not a valid specific type */\n")
         for j, cls in enumerate(of_g.all_class_order):
@@ -112,7 +112,7 @@
 /**
  * Unified map, indexed by wire version which is 1-based.
  */
-int *of_object_to_type_map[OF_VERSION_ARRAY_MAX] = {
+const int *const of_object_to_type_map[OF_VERSION_ARRAY_MAX] = {
     NULL,
 """)
     for version in of_g.of_version_range:
@@ -130,7 +130,7 @@
 """)
     for version in of_g.of_version_range:
         out.write("""
-static of_experimenter_data_t
+static const of_experimenter_data_t
 of_object_to_extension_data_v%d[OF_OBJECT_COUNT] = {
 """ % version)
         out.write("    {0, 0, 0}, /* of_object, not a valid specific type */\n")
@@ -153,7 +153,7 @@
 /**
  * Unified map, indexed by wire version which is 1-based.
  */
-of_experimenter_data_t *of_object_to_extension_data[OF_VERSION_ARRAY_MAX] = {
+const of_experimenter_data_t *const of_object_to_extension_data[OF_VERSION_ARRAY_MAX] = {
     NULL,
 """)
     for version in of_g.of_version_range:
@@ -182,7 +182,7 @@
 
     for i, ar in enumerate(all_ars):
         version = i + 1
-        out.write("static of_object_id_t\nof_%s_v%d[%s] = {\n" %
+        out.write("static const of_object_id_t\nof_%s_v%d[%s] = {\n" %
                   (type_str, version, len_name))
         for i in range(arr_len):
             comma = ""
@@ -210,7 +210,7 @@
  * Indexed by wire version which is 1-based.
  */
 
-of_object_id_t *of_%(name)s[OF_VERSION_ARRAY_MAX] = {
+const of_object_id_t *const of_%(name)s[OF_VERSION_ARRAY_MAX] = {
     NULL,
 """ % dict(name=type_str, c_name=prefix.lower()))
     for version in of_g.of_version_range:
@@ -291,7 +291,7 @@
  * Treat as private; use function accessor below
  */
 
-extern of_object_id_t *of_%(name)s_type_to_id[OF_VERSION_ARRAY_MAX];
+extern const of_object_id_t *const of_%(name)s_type_to_id[OF_VERSION_ARRAY_MAX];
 
 #define OF_%(u_name)s_ITEM_COUNT %(ar_len)d\n
 
@@ -322,7 +322,7 @@
  * Treat as private; use function accessor below
  */
 
-extern of_object_id_t *of_%(name)s_type_to_id[OF_VERSION_ARRAY_MAX];
+extern const of_object_id_t *const of_%(name)s_type_to_id[OF_VERSION_ARRAY_MAX];
 
 #define OF_%(u_name)s_ITEM_COUNT %(ar_len)d\n
 
@@ -357,7 +357,7 @@
  * Treat as private; use function accessor below
  */
 
-extern of_object_id_t *of_%(name)s_type_to_id[OF_VERSION_ARRAY_MAX];
+extern const of_object_id_t *const of_%(name)s_type_to_id[OF_VERSION_ARRAY_MAX];
 
 #define OF_%(u_name)s_ITEM_COUNT %(ar_len)d\n
 
@@ -441,7 +441,7 @@
  * Treat as private; use function accessor below
  */
 
-extern of_object_id_t *of_%(name)s_type_to_id[OF_VERSION_ARRAY_MAX];
+extern const of_object_id_t *const of_%(name)s_type_to_id[OF_VERSION_ARRAY_MAX];
 
 #define OF_%(u_name)s_ITEM_COUNT %(ar_len)d\n
 
@@ -586,7 +586,7 @@
     ################################################################
 
     out.write("""
-extern int *of_object_to_type_map[OF_VERSION_ARRAY_MAX];
+extern const int *const of_object_to_type_map[OF_VERSION_ARRAY_MAX];
 
 /**
  * Map an object ID to its primary wire type value
@@ -632,7 +632,7 @@
 """)
 
     out.write("""
-extern of_experimenter_data_t *of_object_to_extension_data[OF_VERSION_ARRAY_MAX];
+extern const of_experimenter_data_t *const of_object_to_extension_data[OF_VERSION_ARRAY_MAX];
 
 /**
  * Map from the object ID of an extension to the experimenter ID
@@ -792,7 +792,7 @@
     gen_type_to_obj_map_functions(out)
     gen_obj_to_type_map_functions(out)
 
-    out.write("extern int *of_object_fixed_len[OF_VERSION_ARRAY_MAX];\n")
+    out.write("extern const int *const of_object_fixed_len[OF_VERSION_ARRAY_MAX];\n")
 
     out.write("""
 /**
@@ -1038,7 +1038,7 @@
 
     for version in of_g.of_version_range:
         out.write("""
-static int\nof_object_fixed_len_v%d[OF_OBJECT_COUNT] = {
+static const int\nof_object_fixed_len_v%d[OF_OBJECT_COUNT] = {
     -1,   /* of_object is not instantiable */
 """ % version)
         for i, cls in enumerate(of_g.all_class_order):
@@ -1055,7 +1055,7 @@
 /**
  * Unified map of fixed length part of each object
  */
-int *of_object_fixed_len[OF_VERSION_ARRAY_MAX] = {
+const int *const of_object_fixed_len[OF_VERSION_ARRAY_MAX] = {
     NULL,
 """)
     for version in of_g.of_version_range:
@@ -1078,7 +1078,7 @@
  */
 """)
     for version in of_g.of_version_range:
-        out.write("int *of_object_to_stats_type_map_v%d = {\n" % (i+1))
+        out.write("const int *of_object_to_stats_type_map_v%d = {\n" % (i+1))
         out.write("    -1, /* of_object (invalid) */\n");
         for cls in of_g.ordered_messages:
             name = cls[3:]
@@ -1096,7 +1096,7 @@
 /**
  * Unified map, indexed by wire version which is 1-based.
  */
-int *of_object_to_stats_type_map[OF_VERSION_ARRAY_MAX] = {
+const int *of_object_to_stats_type_map[OF_VERSION_ARRAY_MAX] = {
     NULL,
 """)
     for version in of_g.of_version_range:
diff --git a/c_gen/templates/loci_show.h b/c_gen/templates/loci_show.h
index 86efab1..ed7a7d5 100644
--- a/c_gen/templates/loci_show.h
+++ b/c_gen/templates/loci_show.h
@@ -108,6 +108,7 @@
 #define LOCI_SHOW_u16_tcp_dst(writer, cookie, val)         LOCI_SHOW_u16(writer, cookie, val)
 #define LOCI_SHOW_u8_ip_proto(writer, cookie, val)         LOCI_SHOW_u8(writer, cookie, val)
 #define LOCI_SHOW_u64_metadata(writer, cookie, val)        LOCI_SHOW_x64(writer, cookie, val)
+#define LOCI_SHOW_u8_enabled(writer, cookie, val)          LOCI_SHOW_u8(writer, cookie, val)
 
 
 
diff --git a/c_gen/templates/of_type_maps.c b/c_gen/templates/of_type_maps.c
index ef1df37..f1fab57 100644
--- a/c_gen/templates/of_type_maps.c
+++ b/c_gen/templates/of_type_maps.c
@@ -50,8 +50,7 @@
 void
 of_object_message_wire_length_get(of_object_t *obj, int *bytes)
 {
-    of_wire_buffer_t *wbuf = OF_OBJECT_TO_WBUF(obj);
-    ASSERT(wbuf != NULL);
+    ASSERT(OF_OBJECT_TO_WBUF(obj) != NULL);
     // ASSERT(obj is message)
     *bytes = of_message_length_get(OF_OBJECT_TO_MESSAGE(obj));
 }
@@ -65,9 +64,7 @@
 void
 of_object_message_wire_length_set(of_object_t *obj, int bytes)
 {
-    of_wire_buffer_t *wbuf = OF_OBJECT_TO_WBUF(obj);
-    ASSERT(wbuf != NULL);
-
+    ASSERT(OF_OBJECT_TO_WBUF(obj) != NULL);
     // ASSERT(obj is message)
     of_message_length_set(OF_OBJECT_TO_MESSAGE(obj), bytes);
 }
diff --git a/loxi_front_end/of_h_utils.py b/loxi_front_end/of_h_utils.py
index 1259a90..26dbac1 100644
--- a/loxi_front_end/of_h_utils.py
+++ b/loxi_front_end/of_h_utils.py
@@ -65,6 +65,9 @@
     if ident.find("OFPP_") == 0 and version == of_g.VERSION_1_0:
         value_string = "0x%x" % (int(value, 0) + 0xffff0000)
 
+    if ident == 'OFP_VLAN_NONE' and version > of_g.VERSION_1_0:
+        value_string = '0'
+
     # Otherwise, if no reference to a wildcard value, all done
     if value_string.find("OFPFW") < 0:
         return value_string
diff --git a/loxi_front_end/type_maps.py b/loxi_front_end/type_maps.py
index ecfd850..df002c4 100644
--- a/loxi_front_end/type_maps.py
+++ b/loxi_front_end/type_maps.py
@@ -795,6 +795,7 @@
             "of_bsn_shell_status"            : 8,
             "of_bsn_get_interfaces_request"  : 9,
             "of_bsn_get_interfaces_reply"    : 10,
+            "of_bsn_set_pktin_suppression"   : 11,
             },
         nicira = {   # Nicira extensions, value is subtype
             "of_nicira_controller_role_request"      : 10,
@@ -808,6 +809,7 @@
             "of_bsn_get_mirroring_reply"     : 5,
             "of_bsn_get_interfaces_request"  : 9,
             "of_bsn_get_interfaces_reply"    : 10,
+            "of_bsn_set_pktin_suppression"   : 11,
             },
         ),
     of_g.VERSION_1_2:dict(  # Version 1.0 extensions
@@ -817,6 +819,7 @@
             "of_bsn_get_mirroring_reply"     : 5,
             "of_bsn_get_interfaces_request"  : 9,
             "of_bsn_get_interfaces_reply"    : 10,
+            "of_bsn_set_pktin_suppression"   : 11,
             },
         ),
     of_g.VERSION_1_3:dict(  # Version 1.0 extensions
@@ -826,6 +829,7 @@
             "of_bsn_get_mirroring_reply"     : 5,
             "of_bsn_get_interfaces_request"  : 9,
             "of_bsn_get_interfaces_reply"    : 10,
+            "of_bsn_set_pktin_suppression"   : 11,
             },
         ),
 }
diff --git a/loxi_utils/loxi_utils.py b/loxi_utils/loxi_utils.py
index cc3e635..5508c94 100644
--- a/loxi_utils/loxi_utils.py
+++ b/loxi_utils/loxi_utils.py
@@ -33,6 +33,7 @@
 These may need to be sorted out into language specific functions
 """
 
+import sys
 import of_g
 import tenjin
 
@@ -502,7 +503,7 @@
     """
     pp = [ tenjin.PrefixedLinePreprocessor() ] # support "::" syntax
     template_globals = { "to_str": str, "escape": str } # disable HTML escaping
-    engine = tenjin.Engine(path=path, pp=pp)
+    engine = TemplateEngine(path=path, pp=pp)
     out.write(engine.render(name, context, template_globals))
 
 def render_static(out, name, path):
@@ -518,3 +519,18 @@
         raise ValueError("template %s not found" % name)
     with open(template_filename) as infile:
         out.write(infile.read())
+
+class TemplateEngine(tenjin.Engine):
+    def include(self, template_name, **kwargs):
+        """
+        Tenjin has an issue with nested includes that use the same local variable
+        names, because it uses the same context dict for each level of nesting.
+        The fix is to copy the context.
+        """
+        frame = sys._getframe(1)
+        locals  = frame.f_locals
+        globals = frame.f_globals
+        context = locals["_context"].copy()
+        context.update(kwargs)
+        template = self.get_template(template_name, context, globals)
+        return template.render(context, globals, _buf=locals["_buf"])
diff --git a/openflow_input/bsn_pktin_suppression b/openflow_input/bsn_pktin_suppression
new file mode 100644
index 0000000..fcfa4aa
--- /dev/null
+++ b/openflow_input/bsn_pktin_suppression
@@ -0,0 +1,71 @@
+// 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.
+
+#version any
+
+// This extension enables the switch to send only one packet-in for a given flow.
+// The goal is to reduce the load on the controller, particularly in the case of
+// large UDP streams.
+//
+// When this extension is enabled (by sending the bsn_set_pktin_suppression message
+// described below with enabled=1) the switch will install an exact-match drop
+// flow for each packet-in. This flow has the idle_timeout, hard_timeout,
+// priority, and cookie fields set to the values given in the
+// bsn_set_pktin_suppression message.
+//
+// This extension does not affect packet-ins caused by an output to
+// OFPP_CONTROLLER.
+//
+// When this extension is enabled the switch may still send multiple packet-ins
+// for the same flow.
+//
+// This extension keeps its configuration across controller disconnects. When a
+// controller that is aware of this extension connects to a switch it should send
+// a DELETE flow-mod with the cookie field set to ensure that it receives
+// packet-ins for ongoing flows.
+//
+// The flows installed by this extension are not automatically deleted when the
+// extension is disabled. Similarly other configuration changes (cookie, etc) do
+// not affect already-installed flows.
+//
+// The switch should reject the message if both 'hard_timeout' and 'idle_timeout'
+// are zero, since the suppression flows would never expire.
+
+struct ofp_bsn_set_pktin_suppression {
+    uint8_t version;
+    uint8_t type;
+    uint16_t length;
+    uint32_t xid;
+    uint32_t experimenter;    // OF_EXPERIMENTER_ID_BSN
+    uint32_t subtype;         // 11
+    uint8_t enabled;
+    uint8_t pad;
+    uint16_t idle_timeout;
+    uint16_t hard_timeout;
+    uint16_t priority;
+    uint64_t cookie;
+};
diff --git a/py_gen/codegen.py b/py_gen/codegen.py
index 211c71e..67529e0 100644
--- a/py_gen/codegen.py
+++ b/py_gen/codegen.py
@@ -92,6 +92,7 @@
 
         length_member = None
         type_members = []
+        pad_count = 0
 
         for member in unified_class['members']:
             if member['name'] in ['length', 'len']:
@@ -105,9 +106,12 @@
                                                value=type_values[member['name']]))
             else:
                 # HACK ensure member names are unique
-                if member['name'] == "pad" and \
-                [x for x in members if x.name == 'pad']:
-                    m_name = "pad2"
+                if member['name'].startswith("pad"):
+                    if pad_count == 0:
+                        m_name = 'pad'
+                    else:
+                        m_name = "pad%d" % pad_count
+                    pad_count += 1
                 else:
                     m_name = member['name']
                 members.append(Member(name=m_name,
diff --git a/py_gen/templates/_ofclass.py b/py_gen/templates/_ofclass.py
new file mode 100644
index 0000000..ff8b21b
--- /dev/null
+++ b/py_gen/templates/_ofclass.py
@@ -0,0 +1,42 @@
+:: nonskip_members = [m for m in ofclass.members if not m.skip]
+class ${ofclass.pyname}(${superclass}):
+:: for m in ofclass.type_members:
+    ${m.name} = ${m.value}
+:: #endfor
+
+    def __init__(${', '.join(['self'] + ["%s=None" % m.name for m in nonskip_members])}):
+:: for m in nonskip_members:
+        if ${m.name} != None:
+            self.${m.name} = ${m.name}
+        else:
+            self.${m.name} = ${m.oftype.gen_init_expr()}
+:: #endfor
+        return
+
+    def pack(self):
+        packed = []
+:: include("_pack.py", ofclass=ofclass)
+        return ''.join(packed)
+
+    @staticmethod
+    def unpack(buf):
+        obj = ${ofclass.pyname}()
+:: include("_unpack.py", ofclass=ofclass)
+        return obj
+
+    def __eq__(self, other):
+        if type(self) != type(other): return False
+:: for m in nonskip_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)
diff --git a/py_gen/templates/action.py b/py_gen/templates/action.py
index 439e56f..089cc53 100644
--- a/py_gen/templates/action.py
+++ b/py_gen/templates/action.py
@@ -37,66 +37,20 @@
 
 def unpack_list(buf):
     if len(buf) % 8 != 0: raise loxi.ProtocolError("action list length not a multiple of 8")
-    actions = []
-    offset = 0
-    while offset < len(buf):
-        type, length = struct.unpack_from("!HH", buf, offset)
-        if length == 0: raise loxi.ProtocolError("action length is 0")
+    def deserializer(buf):
+        type, length = struct.unpack_from("!HH", buf)
         if length % 8 != 0: raise loxi.ProtocolError("action length not a multiple of 8")
-        if offset + length > len(buf): raise loxi.ProtocolError("action length overruns list length")
         parser = parsers.get(type)
         if not parser: raise loxi.ProtocolError("unknown action type %d" % type)
-        actions.append(parser(buffer(buf, offset, length)))
-        offset += length
-    return actions
+        return parser(buf)
+    return util.unpack_list(deserializer, "!2xH", buf)
 
 class Action(object):
     type = None # override in subclass
     pass
 
 :: for ofclass in ofclasses:
-:: nonskip_members = [m for m in ofclass.members if not m.skip]
-class ${ofclass.pyname}(Action):
-:: for m in ofclass.type_members:
-    ${m.name} = ${m.value}
-:: #endfor
-
-    def __init__(self, ${', '.join(["%s=None" % m.name for m in nonskip_members])}):
-:: for m in nonskip_members:
-        if ${m.name} != None:
-            self.${m.name} = ${m.name}
-        else:
-            self.${m.name} = ${m.oftype.gen_init_expr()}
-:: #endfor
-
-    def pack(self):
-        packed = []
-:: include("_pack.py", ofclass=ofclass)
-        return ''.join(packed)
-
-    @staticmethod
-    def unpack(buf):
-        obj = ${ofclass.pyname}()
-:: include("_unpack.py", ofclass=ofclass)
-        return obj
-
-    def __eq__(self, other):
-        if type(self) != type(other): return False
-        if self.type != other.type: return False
-:: for m in nonskip_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)
+:: include('_ofclass.py', ofclass=ofclass, superclass="Action")
 
 :: #endfor
 
diff --git a/py_gen/templates/common.py b/py_gen/templates/common.py
index 08fb65d..c9af309 100644
--- a/py_gen/templates/common.py
+++ b/py_gen/templates/common.py
@@ -39,87 +39,23 @@
 common = sys.modules[__name__]
 
 def unpack_list_flow_stats_entry(buf):
-    entries = []
-    offset = 0
-    while offset < len(buf):
-        length, = struct.unpack_from("!H", buf, offset)
-        if length == 0: raise loxi.ProtocolError("entry length is 0")
-        if offset + length > len(buf): raise loxi.ProtocolError("entry length overruns list length")
-        entries.append(flow_stats_entry.unpack(buffer(buf, offset, length)))
-        offset += length
-    return entries
+    return util.unpack_list(flow_stats_entry.unpack, "!H", buf)
 
 def unpack_list_queue_prop(buf):
-    entries = []
-    offset = 0
-    while offset < len(buf):
-        type, length, = struct.unpack_from("!HH", buf, offset)
-        if length == 0: raise loxi.ProtocolError("entry length is 0")
-        if offset + length > len(buf): raise loxi.ProtocolError("entry length overruns list length")
+    def deserializer(buf):
+        type, = struct.unpack_from("!H", buf)
         if type == const.OFPQT_MIN_RATE:
-            entry = queue_prop_min_rate.unpack(buffer(buf, offset, length))
+            return queue_prop_min_rate.unpack(buf)
         else:
             raise loxi.ProtocolError("unknown queue prop %d" % type)
-        entries.append(entry)
-        offset += length
-    return entries
+    return util.unpack_list(deserializer, "!2xH", buf)
 
 def unpack_list_packet_queue(buf):
-    entries = []
-    offset = 0
-    while offset < len(buf):
-        _, length, = struct.unpack_from("!LH", buf, offset)
-        if length == 0: raise loxi.ProtocolError("entry length is 0")
-        if offset + length > len(buf): raise loxi.ProtocolError("entry length overruns list length")
-        entries.append(packet_queue.unpack(buffer(buf, offset, length)))
-        offset += length
-    return entries
+    return util.unpack_list(packet_queue.unpack, "!4xH", buf)
 
 :: for ofclass in ofclasses:
-class ${ofclass.pyname}(object):
-:: for m in ofclass.type_members:
-    ${m.name} = ${m.value}
+:: include('_ofclass.py', ofclass=ofclass, superclass="object")
+
 :: #endfor
 
-    def __init__(self, ${', '.join(["%s=None" % m.name for m in ofclass.members])}):
-:: for m in ofclass.members:
-        if ${m.name} != None:
-            self.${m.name} = ${m.name}
-        else:
-            self.${m.name} = ${m.oftype.gen_init_expr()}
-:: #endfor
-
-    def pack(self):
-        packed = []
-:: include("_pack.py", ofclass=ofclass)
-        return ''.join(packed)
-
-    @staticmethod
-    def unpack(buf):
-        assert(len(buf) >= ${ofclass.min_length}) # Should be verified by caller
-        obj = ${ofclass.pyname}()
-:: include("_unpack.py", ofclass=ofclass)
-        return obj
-
-    def __eq__(self, other):
-        if type(self) != type(other): return False
-:: for m in ofclass.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)
-
-:: if ofclass.name.startswith("of_match_v"):
-match = ${ofclass.pyname}
-
-:: #endif
-:: #endfor
+match = match_v1
diff --git a/py_gen/templates/util.py b/py_gen/templates/util.py
index 5933e14..fead78f 100644
--- a/py_gen/templates/util.py
+++ b/py_gen/templates/util.py
@@ -31,6 +31,7 @@
 
 import loxi
 import const
+import struct
 
 def unpack_array(deserializer, element_size, buf):
     """
@@ -41,6 +42,26 @@
     n = len(buf) / element_size
     return [deserializer(buffer(buf, i*element_size, element_size)) for i in range(n)]
 
+def unpack_list(deserializer, length_fmt, buf):
+    """
+    Deserialize a list of variable-length entries.
+    'length_fmt' is a struct format string with exactly one non-padding format
+    character that returns the length of the given element.
+    The deserializer function should take a buffer and return the new object.
+    """
+    entries = []
+    offset = 0
+    length_struct = struct.Struct(length_fmt)
+    n = len(buf)
+    while offset < n:
+        if offset + length_struct.size > len(buf): raise loxi.ProtocolError("entry header overruns list length")
+        length, = length_struct.unpack_from(buf, offset)
+        if length < length_struct.size: raise loxi.ProtocolError("entry length is less than the header length")
+        if offset + length > len(buf): raise loxi.ProtocolError("entry length overruns list length")
+        entries.append(deserializer(buffer(buf, offset, length)))
+        offset += length
+    return entries
+
 def pretty_mac(mac):
     return ':'.join(["%02x" % x for x in mac])
 
diff --git a/py_gen/tests.py b/py_gen/tests/of10.py
similarity index 95%
rename from py_gen/tests.py
rename to py_gen/tests/of10.py
index 23d40b8..86f9b2b 100644
--- a/py_gen/tests.py
+++ b/py_gen/tests/of10.py
@@ -28,8 +28,7 @@
 import unittest
 
 try:
-    import loxi
-    del loxi
+    import loxi.of10 as ofp
 except ImportError:
     exit("loxi package not found. Try setting PYTHONPATH.")
 
@@ -45,7 +44,7 @@
         self.assertTrue(hasattr(ofp, "message"))
 
     def test_version(self):
-        import loxi.of10
+        import loxi
         self.assertTrue(hasattr(loxi.of10, "ProtocolError"))
         self.assertTrue(hasattr(loxi.of10, "OFP_VERSION"))
         self.assertEquals(loxi.of10.OFP_VERSION, 1)
@@ -56,14 +55,11 @@
 
 class TestActions(unittest.TestCase):
     def test_output_pack(self):
-        import loxi.of10 as ofp
         expected = "\x00\x00\x00\x08\xff\xf8\xff\xff"
         action = ofp.action.output(port=ofp.OFPP_IN_PORT, max_len=0xffff)
         self.assertEquals(expected, action.pack())
 
     def test_output_unpack(self):
-        import loxi.of10 as ofp
-
         # Normal case
         buf = "\x00\x00\x00\x08\xff\xf8\xff\xff"
         action = ofp.action.output.unpack(buf)
@@ -76,7 +72,6 @@
             ofp.action.output.unpack(buf)
 
     def test_output_equality(self):
-        import loxi.of10 as ofp
         action = ofp.action.output(port=1, max_len=0x1234)
         action2 = ofp.action.output(port=1, max_len=0x1234)
         self.assertEquals(action, action2)
@@ -90,13 +85,11 @@
         action2.max_len = 0x1234
 
     def test_output_show(self):
-        import loxi.of10 as ofp
         action = ofp.action.output(port=1, max_len=0x1234)
         expected = "output { port = 1, max_len = 0x1234 }"
         self.assertEquals(expected, action.show())
 
     def test_bsn_set_tunnel_dst_pack(self):
-        import loxi.of10 as ofp
         expected = ''.join([
             "\xff\xff", "\x00\x10", # type/length
             "\x00\x5c\x16\xc7", # experimenter
@@ -107,7 +100,6 @@
         self.assertEquals(expected, action.pack())
 
     def test_bsn_set_tunnel_dst_unpack(self):
-        import loxi.of10 as ofp
         buf = ''.join([
             "\xff\xff", "\x00\x10", # type/length
             "\x00\x5c\x16\xc7", # experimenter
@@ -121,8 +113,6 @@
 # Assumes action serialization/deserialization works
 class TestActionList(unittest.TestCase):
     def test_normal(self):
-        import loxi.of10 as ofp
-
         expected = []
         bufs = []
 
@@ -140,20 +130,16 @@
         self.assertEquals(actions, expected)
 
     def test_empty_list(self):
-        import loxi.of10 as ofp
         self.assertEquals(ofp.action.unpack_list(''), [])
 
     def test_invalid_list_length(self):
-        import loxi.of10 as ofp
         buf = '\x00' * 9
         with self.assertRaisesRegexp(ofp.ProtocolError, 'not a multiple of 8'):
             ofp.action.unpack_list(buf)
 
     def test_invalid_action_length(self):
-        import loxi.of10 as ofp
-
         buf = '\x00' * 8
-        with self.assertRaisesRegexp(ofp.ProtocolError, 'is 0'):
+        with self.assertRaisesRegexp(ofp.ProtocolError, 'is less than the header length'):
             ofp.action.unpack_list(buf)
 
         buf = '\x00\x00\x00\x04'
@@ -165,23 +151,19 @@
             ofp.action.unpack_list(buf)
 
     def test_invalid_action_type(self):
-        import loxi.of10 as ofp
         buf = '\xff\xfe\x00\x08\x00\x00\x00\x00'
         with self.assertRaisesRegexp(ofp.ProtocolError, 'unknown action type'):
             ofp.action.unpack_list(buf)
 
 class TestConstants(unittest.TestCase):
     def test_ports(self):
-        import loxi.of10 as ofp
         self.assertEquals(0xffff, ofp.OFPP_NONE)
 
     def test_wildcards(self):
-        import loxi.of10 as ofp
         self.assertEquals(0xfc000, ofp.OFPFW_NW_DST_MASK)
 
 class TestCommon(unittest.TestCase):
     def test_port_desc_pack(self):
-        import loxi.of10 as ofp
         obj = ofp.port_desc(port_no=ofp.OFPP_CONTROLLER,
                             hw_addr=[1,2,3,4,5,6],
                             name="foo",
@@ -205,7 +187,6 @@
         self.assertEquals(expected, obj.pack())
 
     def test_port_desc_unpack(self):
-        import loxi.of10 as ofp
         buf = ''.join([
             '\xff\xfd', # port_no
             '\x01\x02\x03\x04\x05\x06', # hw_addr
@@ -223,7 +204,6 @@
         self.assertEquals(ofp.OFPPF_PAUSE_ASYM, obj.peer)
 
     def test_table_stats_entry_pack(self):
-        import loxi.of10 as ofp
         obj = ofp.table_stats_entry(table_id=3,
                                     name="foo",
                                     wildcards=ofp.OFPFW_ALL,
@@ -244,7 +224,6 @@
         self.assertEquals(expected, obj.pack())
 
     def test_table_stats_entry_unpack(self):
-        import loxi.of10 as ofp
         buf = ''.join([
             '\x03', # table_id
             '\x00\x00\x00', # pad
@@ -261,7 +240,6 @@
         self.assertEquals(9300233470495232273L, obj.matched_count)
 
     def test_flow_stats_entry_pack(self):
-        import loxi.of10 as ofp
         obj = ofp.flow_stats_entry(table_id=3,
                                    match=ofp.match(),
                                    duration_sec=1,
@@ -301,7 +279,6 @@
         self.assertEquals(expected, obj.pack())
 
     def test_flow_stats_entry_unpack(self):
-        import loxi.of10 as ofp
         buf = ''.join([
             '\x00\x68', # length
             '\x03', # table_id
@@ -334,7 +311,6 @@
         self.assertEquals(2, obj.actions[1].port)
 
     def test_match(self):
-        import loxi.of10 as ofp
         match = ofp.match()
         self.assertEquals(match.wildcards, ofp.OFPFW_ALL)
         self.assertEquals(match.tcp_src, 0)
@@ -344,8 +320,6 @@
 
 class TestMessages(unittest.TestCase):
     def test_hello_construction(self):
-        import loxi.of10 as ofp
-
         msg = ofp.message.hello()
         self.assertEquals(msg.version, ofp.OFP_VERSION)
         self.assertEquals(msg.type, ofp.OFPT_HELLO)
@@ -359,8 +333,6 @@
         self.assertEquals(msg.xid, 0)
 
     def test_hello_unpack(self):
-        import loxi.of10 as ofp
-
         # Normal case
         buf = "\x01\x00\x00\x08\x12\x34\x56\x78"
         msg = ofp.message.hello(xid=0x12345678)
@@ -372,13 +344,10 @@
             ofp.message.hello.unpack(buf)
 
     def test_echo_request_construction(self):
-        import loxi.of10 as ofp
         msg = ofp.message.echo_request(data="abc")
         self.assertEquals(msg.data, "abc")
 
     def test_echo_request_pack(self):
-        import loxi.of10 as ofp
-
         msg = ofp.message.echo_request(xid=0x12345678, data="abc")
         buf = msg.pack()
         self.assertEquals(buf, "\x01\x02\x00\x0b\x12\x34\x56\x78\x61\x62\x63")
@@ -387,8 +356,6 @@
         self.assertEquals(msg, msg2)
 
     def test_echo_request_unpack(self):
-        import loxi.of10 as ofp
-
         # Normal case
         buf = "\x01\x02\x00\x0b\x12\x34\x56\x78\x61\x62\x63"
         msg = ofp.message.echo_request(xid=0x12345678, data="abc")
@@ -400,8 +367,6 @@
             ofp.message.echo_request.unpack(buf)
 
     def test_echo_request_equality(self):
-        import loxi.of10 as ofp
-
         msg = ofp.message.echo_request(xid=0x12345678, data="abc")
         msg2 = ofp.message.echo_request(xid=0x12345678, data="abc")
         #msg2 = ofp.message.echo_request.unpack(msg.pack())
@@ -416,13 +381,11 @@
         msg2.data = msg.data
 
     def test_echo_request_show(self):
-        import loxi.of10 as ofp
         expected = "echo_request { xid = 0x12345678, data = 'ab\\x01' }"
         msg = ofp.message.echo_request(xid=0x12345678, data="ab\x01")
         self.assertEquals(msg.show(), expected)
 
     def test_flow_add(self):
-        import loxi.of10 as ofp
         match = ofp.match()
         msg = ofp.message.flow_add(xid=1,
                                    match=match,
@@ -439,7 +402,6 @@
         self.assertEquals(msg, msg2)
 
     def test_port_mod_pack(self):
-        import loxi.of10 as ofp
         msg = ofp.message.port_mod(xid=2,
                                    port_no=ofp.OFPP_CONTROLLER,
                                    hw_addr=[1,2,3,4,5,6],
@@ -450,7 +412,6 @@
         self.assertEquals(expected, msg.pack())
 
     def test_desc_stats_reply_pack(self):
-        import loxi.of10 as ofp
         msg = ofp.message.desc_stats_reply(xid=3,
                                            flags=ofp.OFPSF_REPLY_MORE,
                                            mfr_desc="The Indigo-2 Community",
@@ -473,7 +434,6 @@
         self.assertEquals(expected, msg.pack())
 
     def test_desc_stats_reply_unpack(self):
-        import loxi.of10 as ofp
         buf = ''.join([
             '\x01', '\x11', # version/type
             '\x04\x2c', # length
@@ -493,8 +453,6 @@
         self.assertEquals(ofp.OFPSF_REPLY_MORE, msg.flags)
 
     def test_port_status_pack(self):
-        import loxi.of10 as ofp
-
         desc = ofp.port_desc(port_no=ofp.OFPP_CONTROLLER,
                              hw_addr=[1,2,3,4,5,6],
                              name="foo",
@@ -527,7 +485,6 @@
         self.assertEquals(expected, msg.pack())
 
     def test_port_status_unpack(self):
-        import loxi.of10 as ofp
         buf = ''.join([
             '\x01', '\x0c', # version/type
             '\x00\x40', # length
@@ -549,7 +506,6 @@
         self.assertEquals(ofp.OFPPF_PAUSE_ASYM, msg.desc.peer)
 
     def test_port_stats_reply_pack(self):
-        import loxi.of10 as ofp
         msg = ofp.message.port_stats_reply(xid=5, flags=0, entries=[
             ofp.port_stats_entry(port_no=1, rx_packets=56, collisions=5),
             ofp.port_stats_entry(port_no=ofp.OFPP_LOCAL, rx_packets=1, collisions=1)])
@@ -591,7 +547,6 @@
         self.assertEquals(expected, msg.pack())
 
     def test_port_stats_reply_unpack(self):
-        import loxi.of10 as ofp
         buf = ''.join([
             '\x01', '\x11', # version/type
             '\x00\xdc', # length
@@ -688,7 +643,6 @@
     ])
 
     def test_flow_stats_reply_pack(self):
-        import loxi.of10 as ofp
         msg = ofp.message.flow_stats_reply(xid=6, flags=0, entries=[
             ofp.flow_stats_entry(table_id=3,
                                  match=ofp.match(),
@@ -718,7 +672,6 @@
         self.assertEquals(self.sample_flow_stats_reply_buf, msg.pack())
 
     def test_flow_stats_reply_unpack(self):
-        import loxi.of10 as ofp
         msg = ofp.message.flow_stats_reply.unpack(self.sample_flow_stats_reply_buf)
         self.assertEquals(ofp.OFPST_FLOW, msg.stats_type)
         self.assertEquals(2, len(msg.entries))
@@ -726,7 +679,6 @@
         self.assertEquals(3, len(msg.entries[1].actions))
 
     def test_flow_add_show(self):
-        import loxi.of10 as ofp
         expected = """\
 flow_add {
   xid = None,
@@ -737,11 +689,11 @@
     eth_dst = cd:ef:01:23:45:67,
     vlan_vid = 0x0,
     vlan_pcp = 0x0,
-    pad1 = 0x0,
+    pad = 0x0,
     eth_type = 0x0,
     ip_dscp = 0x0,
     ip_proto = 0x0,
-    pad2 = [ 0, 0 ],
+    pad1 = [ 0, 0 ],
     ipv4_src = 192.168.3.127,
     ipv4_dst = 255.255.255.255,
     tcp_src = 0x0,
@@ -756,7 +708,7 @@
   flags = 0x0,
   actions = [
     output { port = OFPP_FLOOD, max_len = 0x0 },
-    nicira_dec_ttl { pad = 0x0, pad2 = 0x0 },
+    nicira_dec_ttl { pad = 0x0, pad1 = 0x0 },
     bsn_set_tunnel_dst { dst = 0x0 }
   ]
 }"""
@@ -793,7 +745,6 @@
     ])
 
     def test_packet_out_pack(self):
-        import loxi.of10 as ofp
         msg = ofp.message.packet_out(
             xid=0x12345678,
             buffer_id=0xabcdef01,
@@ -805,7 +756,6 @@
         self.assertEquals(self.sample_packet_out_buf, msg.pack())
 
     def test_packet_out_unpack(self):
-        import loxi.of10 as ofp
         msg = ofp.message.packet_out.unpack(self.sample_packet_out_buf)
         self.assertEquals(0x12345678, msg.xid)
         self.assertEquals(0xabcdef01, msg.buffer_id)
@@ -828,7 +778,6 @@
     ])
 
     def test_packet_in_pack(self):
-        import loxi.of10 as ofp
         msg = ofp.message.packet_in(
             xid=0x12345678,
             buffer_id=0xabcdef01,
@@ -839,7 +788,6 @@
         self.assertEquals(self.sample_packet_in_buf, msg.pack())
 
     def test_packet_in_unpack(self):
-        import loxi.of10 as ofp
         msg = ofp.message.packet_in.unpack(self.sample_packet_in_buf)
         self.assertEquals(0x12345678, msg.xid)
         self.assertEquals(0xabcdef01, msg.buffer_id)
@@ -878,7 +826,6 @@
     ])
 
     def test_queue_get_config_reply_pack(self):
-        import loxi.of10 as ofp
         msg = ofp.message.queue_get_config_reply(
             xid=0x12345678,
             port=ofp.OFPP_LOCAL,
@@ -891,7 +838,6 @@
         self.assertEquals(self.sample_queue_get_config_reply_buf, msg.pack())
 
     def test_queue_get_config_reply_unpack(self):
-        import loxi.of10 as ofp
         msg = ofp.message.queue_get_config_reply.unpack(self.sample_queue_get_config_reply_buf)
         self.assertEquals(ofp.OFPP_LOCAL, msg.port)
         self.assertEquals(msg.queues[0].queue_id, 1)
@@ -903,7 +849,6 @@
 class TestParse(unittest.TestCase):
     def test_parse_header(self):
         import loxi
-        import loxi.of10 as ofp
 
         msg_ver, msg_type, msg_len, msg_xid = ofp.message.parse_header("\x01\x04\xAF\xE8\x12\x34\x56\x78")
         self.assertEquals(1, msg_ver)
@@ -943,7 +888,6 @@
             util.unpack_array(str, 3, "abcdefgh")
 
     def test_pretty_wildcards(self):
-        import loxi.of10 as ofp
         self.assertEquals("OFPFW_ALL", ofp.util.pretty_wildcards(ofp.OFPFW_ALL))
         self.assertEquals("0", ofp.util.pretty_wildcards(0))
         self.assertEquals("OFPFW_DL_SRC|OFPFW_DL_DST",
@@ -963,7 +907,6 @@
     """
 
     def setUp(self):
-        import loxi.of10 as ofp
         mods = [ofp.action,ofp.message,ofp.common]
         self.klasses = [klass for mod in mods
                               for klass in mod.__dict__.values()
@@ -971,7 +914,6 @@
         self.klasses.sort(key=lambda x: str(x))
 
     def test_serialization(self):
-        import loxi.of10 as ofp
         expected_failures = []
         for klass in self.klasses:
             def fn():
@@ -986,7 +928,6 @@
                 fn()
 
     def test_show(self):
-        import loxi.of10 as ofp
         expected_failures = []
         for klass in self.klasses:
             def fn():