Merge pull request #330 from rlane/of14-loci2

[of14] OF 1.4 support for LOCI
diff --git a/c_gen/build_of_g.py b/c_gen/build_of_g.py
index 047b254..ff2dfe8 100755
--- a/c_gen/build_of_g.py
+++ b/c_gen/build_of_g.py
@@ -119,135 +119,6 @@
         if not m_name in of_g.ordered_members[cls]:
             of_g.ordered_members[cls].append(m_name)
 
-def update_offset(cls, wire_version, name, offset, m_type):
-    """
-    Update (and return) the offset based on type.
-    @param cls The parent class
-    @param wire_version The wire version being processed
-    @param name The name of the data member
-    @param offset The current offset
-    @param m_type The type declaration being processed
-    @returns A pair (next_offset, len_update)  next_offset is the new offset
-    of the next object or -1 if this is a var-length object.  len_update
-    is the increment that should be added to the length.  Note that (for
-    of_match_v3) it is variable length, but it adds 8 bytes to the fixed
-    length of the object
-    If offset is already -1, do not update
-    Otherwise map to base type and count and update (if possible)
-    """
-    if offset < 0:    # Don't update offset once set to -1
-        return offset, 0
-
-    count, base_type = loxi_utils.type_dec_to_count_base(m_type)
-
-    len_update = 0
-    if base_type in of_g.of_mixed_types:
-        base_type = of_g.of_mixed_types[base_type][wire_version]
-
-    base_class = base_type[:-2]
-    if (base_class, wire_version) in of_g.is_fixed_length:
-        bytes = of_g.base_length[(base_class, wire_version)]
-    else:
-        if base_type == "of_match_v3_t":
-            # This is a special case: it has non-zero min length
-            # but is variable length
-            bytes = -1
-            len_update = 8
-        elif base_type == "of_oxm_header_t":
-            # This is a special case: it has non-zero min length
-            # but is variable length
-            bytes = -1
-            len_update = 4
-        elif base_type == "of_bsn_vport_header_t":
-            # This is a special case: it has non-zero min length
-            # but is variable length
-            bytes = -1
-            len_update = 4
-        elif base_type in of_g.of_base_types:
-            bytes = of_g.of_base_types[base_type]["bytes"]
-        else:
-            print "UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type)
-            log("UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type))
-            bytes = -1
-
-    # If bytes
-    if bytes > 0:
-        len_update = count * bytes
-
-    if bytes == -1:
-        return -1, len_update
-
-    return offset + (count * bytes), len_update
-
-def calculate_offsets_and_lengths(ordered_classes, classes, wire_version):
-    """
-    Generate the offsets for fixed offset class members
-    Also calculate the class_sizes when possible.
-
-    @param classes The classes to process
-    @param wire_version The wire version for this set of classes
-
-    Updates global variables
-    """
-
-    lists = set()
-
-    # Generate offsets
-    for cls in ordered_classes:
-        fixed_offset = 0 # The last "good" offset seen
-        offset = 0
-        last_offset = 0
-        last_name = "-"
-        for member in classes[cls]:
-            m_type = member["m_type"]
-            name = member["name"]
-            if last_offset == -1:
-                if name == "pad":
-                    log("Skipping pad for special offset for %s" % cls)
-                else:
-                    log("SPECIAL OFS: Member %s (prev %s), class %s ver %d" %
-                          (name, last_name, cls, wire_version))
-                    if (((cls, name) in of_g.special_offsets) and
-                        (of_g.special_offsets[(cls, name)] != last_name)):
-                        debug("ERROR: special offset prev name changed")
-                        debug("  cls %s. name %s. version %d. was %s. now %s" %
-                              cls, name, wire_version,
-                              of_g.special_offsets[(cls, name)], last_name)
-                        sys.exit(1)
-                    of_g.special_offsets[(cls, name)] = last_name
-
-            member["offset"] = offset
-            if m_type.find("list(") == 0:
-                (list_name, base_type) = loxi_utils.list_name_extract(m_type)
-                lists.add(list_name)
-                member["m_type"] = list_name + "_t"
-                offset = -1
-            elif m_type.find("struct") == 0:
-                debug("ERROR found struct: %s.%s " % (cls, name))
-                sys.exit(1)
-            elif m_type == "octets":
-                log("offset gen skipping octets: %s.%s " % (cls, name))
-                offset = -1
-            else:
-                offset, len_update = update_offset(cls, wire_version, name,
-                                                  offset, m_type)
-                if offset != -1:
-                    fixed_offset = offset
-                else:
-                    fixed_offset += len_update
-                    log("offset is -1 for %s.%s version %d " %
-                        (cls, name, wire_version))
-            last_offset = offset
-            last_name = name
-        of_g.base_length[(cls, wire_version)] = fixed_offset
-        if (offset != -1):
-            of_g.is_fixed_length.add((cls, wire_version))
-
-    for list_type in lists:
-        classes[list_type] = []
-        of_g.ordered_classes[wire_version].append(list_type)
-        of_g.base_length[(list_type, wire_version)] = 0
-
 def order_and_assign_object_ids():
     """
     Order all classes and assign object ids to all classes.
@@ -329,11 +200,7 @@
             pad_count = 0
             for m in ofclass.members:
                 if type(m) == OFPadMember:
-                    m_name = 'pad%d' % pad_count
-                    if m_name == 'pad0': m_name = 'pad'
-                    legacy_members.append(dict(m_type='uint8_t[%d]' % m.length,
-                                               name=m_name))
-                    pad_count += 1
+                    continue
                 else:
                     # HACK the C backend does not yet support of_oxm_t
                     if m.oftype == 'of_oxm_t':
@@ -341,15 +208,28 @@
                     # HACK the C backend does not yet support of_bsn_vport_t
                     elif m.oftype == 'of_bsn_vport_t':
                         m_type = 'of_bsn_vport_header_t'
+                    elif m.oftype.find("list(") == 0:
+                        (list_name, base_type) = loxi_utils.list_name_extract(m.oftype)
+                        m_type = list_name + "_t"
                     else:
                         enum = find(lambda e: e.name == m.oftype, protocol.enums)
                         if enum and "wire_type" in enum.params:
                             m_type = enum.params["wire_type"]
                         else:
                             m_type = m.oftype
-                    legacy_members.append(dict(m_type=m_type, name=m.name))
+
+                    if m.offset is None:
+                        m_offset = -1
+                    else:
+                        m_offset = m.offset
+
+                    legacy_members.append(dict(m_type=m_type, name=m.name, offset=m_offset))
             versions[version_name]['classes'][ofclass.name] = legacy_members
 
+            of_g.base_length[(ofclass.name, version.wire_version)] = ofclass.base_length
+            if ofclass.is_fixed_length:
+                of_g.is_fixed_length.add((ofclass.name, version.wire_version))
+
         for enum in protocol.enums:
             for entry in enum.entries:
                 identifiers.add_identifier(
@@ -377,13 +257,42 @@
                 new_cls = cls + '_header'
                 of_g.ordered_classes[wire_version].append(new_cls)
                 classes[new_cls] = classes[cls]
+                of_g.base_length[(new_cls, wire_version)] = of_g.base_length[(cls, wire_version)]
+                if (cls, wire_version) in of_g.is_fixed_length:
+                    of_g.is_fixed_length.add((cls, wire_version))
 
-    for wire_version in of_g.wire_ver_map.keys():
-        version_name = of_g.of_version_wire2name[wire_version]
-        calculate_offsets_and_lengths(
-            of_g.ordered_classes[wire_version],
-            versions[version_name]['classes'],
-            wire_version)
+    # Create lists
+    for version, protocol in loxi_globals.ir.items():
+        lists = set()
+        classes = versions[of_g.of_version_wire2name[version.wire_version]]['classes']
+
+        for ofclass in protocol.classes:
+            for m in ofclass.members:
+                if isinstance(m, OFDataMember) and m.oftype.find("list(") == 0:
+                    (list_name, base_type) = loxi_utils.list_name_extract(m.oftype)
+                    lists.add(list_name)
+
+        for list_type in lists:
+            classes[list_type] = []
+            of_g.ordered_classes[version.wire_version].append(list_type)
+            of_g.base_length[(list_type, version.wire_version)] = 0
+
+    # Find special offsets
+    # These are defined as members (except padding) that don't have a fixed
+    # offset. The special_offsets map stores the name of the previous member.
+    for version, protocol in loxi_globals.ir.items():
+        for ofclass in protocol.classes:
+            prev_member = None
+            for m in ofclass.members:
+                if isinstance(m, OFPadMember):
+                    continue
+                if m.offset == None:
+                    old = of_g.special_offsets.get((ofclass.name, m.name))
+                    if old and old != prev_member.name:
+                        raise Exception("Error: special offset changed: version=%s cls=%s member=%s old=%s new=%s" %
+                                        (version, ofclass.name, m.name, old, prev_member.name))
+                    of_g.special_offsets[(ofclass.name, m.name)] = prev_member.name
+                prev_member = m
 
 def unify_input():
     """
diff --git a/c_gen/c_code_gen.py b/c_gen/c_code_gen.py
index 3229403..b708c65 100644
--- a/c_gen/c_code_gen.py
+++ b/c_gen/c_code_gen.py
@@ -403,7 +403,6 @@
 int of_object_wire_init(of_object_t *obj, of_object_id_t base_object_id, int max_len);
 
 extern const int *const of_object_fixed_len[OF_VERSION_ARRAY_MAX];
-extern const int *const of_object_extra_len[OF_VERSION_ARRAY_MAX];
 """)
     c_type_maps.gen_type_data_header(out)
     c_match.gen_declarations(out)
@@ -530,7 +529,7 @@
 /**
  * Check if a version is supported
  */
-#define OF_VERSION_OKAY(v) ((v) >= OF_VERSION_1_0 && (v) <= OF_VERSION_1_3)
+#define OF_VERSION_OKAY(v) ((v) >= OF_VERSION_1_0 && (v) <= OF_VERSION_1_4)
 
 """)
     gen_version_enum(out)
@@ -725,24 +724,6 @@
 #define U64_HTON(val) U64_NTOH(val)
 #define IPV6_HTON(dst, src) /* NOTE different syntax; currently no-op */
 #endif
-
-/****************************************************************
- *
- * The following are internal definitions used by the automatically
- * generated code.  Users should not reference these definitions
- * as they may change between versions of this code
- *
- ****************************************************************/
-
-#define OF_MESSAGE_IN_MATCH_POINTER(obj)                            \\
-    (WIRE_BUF_POINTER(&((obj)->wire_buffer), OF_MESSAGE_IN_MATCH_OFFSET))
-#define OF_MESSAGE_IN_MATCH_LEN(ptr) BUF_U16_GET(&ptr[2])
-#define OF_MESSAGE_IN_DATA_OFFSET(obj) \\
-    (FIXED_LEN + OF_MESSAGE_IN_MATCH_LEN(OF_MESSAGE_IN_MATCH_POINTER(obj)) + 2)
-
-#define OF_MESSAGE_OUT_DATA_OFFSET(obj) \\
-    (FIXED_LEN + of_message_out_actions_len_get(obj))
-
 """)
 
 def external_h_top_matter(out, name):
@@ -1618,7 +1599,7 @@
         MEMSET(obj, 0, sizeof(*obj));
     }
     if (bytes < 0) {
-        bytes = of_object_fixed_len[version][%(enum)s] + of_object_extra_len[version][%(enum)s];
+        bytes = of_object_fixed_len[version][%(enum)s];
     }
     obj->version = version;
     obj->length = bytes;
@@ -1637,64 +1618,6 @@
 
 """)
 
-## @fixme This should also be updated once there is a map from
-# class instance to wire length/type accessors
-def gen_wire_push_fn(cls, out):
-    """
-    Generate the calls to push values into the wire buffer
-    """
-    if type_maps.class_is_virtual(cls):
-        print "Push fn gen called for virtual class " + cls
-        return
-
-    out.write("""
-/**
- * Helper function to push values into the wire buffer
- */
-static inline int
-%(cls)s_push_wire_values(%(cls)s_t *obj)
-{
-""" % dict(cls=cls))
-
-    import loxi_globals
-    uclass = loxi_globals.unified.class_by_name(cls)
-    if uclass and not uclass.virtual and uclass.has_type_members:
-        out.write("""
-    %(cls)s_push_wire_types(obj);
-""" % dict(cls=cls))
-
-    if loxi_utils.class_is_message(cls):
-        out.write("""
-    /* Message obj; set length */
-    of_message_t msg;
-
-    if ((msg = OF_OBJECT_TO_MESSAGE(obj)) != NULL) {
-        of_message_length_set(msg, obj->length);
-    }
-""" % dict(name = enum_name(cls)))
-
-    else: # Not a message
-        if loxi_utils.class_is_tlv16(cls):
-            out.write("""
-    /* TLV obj; set length */
-    of_tlv16_wire_length_set((of_object_t *)obj, obj->length);
-""" % dict(enum=enum_name(cls)))
-
-        if loxi_utils.class_is_u16_len(cls) or cls == "of_packet_queue":
-            out.write("""
-    of_object_wire_length_set((of_object_t *)obj, obj->length);
-""")
-
-        if cls == "of_meter_stats":
-            out.write("""
-    of_meter_stats_wire_length_set((of_object_t *)obj, obj->length);
-""" % dict(enum=enum_name(cls)))
-
-    out.write("""
-    return OF_ERROR_NONE;
-}
-""")
-
 def gen_new_fn_body(cls, out):
     """
     Generate function body for new function
@@ -1708,9 +1631,6 @@
  */
 """ % dict(cls=cls))
 
-    if not type_maps.class_is_virtual(cls):
-        gen_wire_push_fn(cls, out)
-
     uclass = loxi_globals.unified.class_by_name(cls)
     is_fixed_length = uclass and uclass.is_fixed_length
     max_length = is_fixed_length and "bytes" or "OF_WIRE_BUFFER_MAX_LENGTH"
@@ -1734,7 +1654,7 @@
     %(cls)s_t *obj;
     int bytes;
 
-    bytes = of_object_fixed_len[version][%(enum)s] + of_object_extra_len[version][%(enum)s];
+    bytes = of_object_fixed_len[version][%(enum)s];
 
     if ((obj = (%(cls)s_t *)of_object_new(%(max_length)s)) == NULL) {
         return NULL;
@@ -1743,20 +1663,25 @@
     %(cls)s_init(obj, version, bytes, 0);
 """ % dict(cls=cls, enum=enum_name(cls), max_length=max_length))
     if not type_maps.class_is_virtual(cls):
-        out.write("""
-    if (%(cls)s_push_wire_values(obj) < 0) {
-        FREE(obj);
-        return NULL;
-    }
-""" % dict(cls=cls))
+        from codegen import class_metadata_dict
+        metadata = class_metadata_dict[cls]
+
+        if metadata.wire_type_set != 'NULL':
+            out.write("""\
+    %s(obj);
+""" % metadata.wire_type_set)
+
+        if metadata.wire_length_set != 'NULL':
+            out.write("""\
+    %s(obj, obj->length);
+""" % metadata.wire_length_set)
 
     match_offset = v3_match_offset_get(cls)
     if match_offset >= 0:
         # Init length field for match object
         out.write("""
     /* Initialize match TLV for 1.2 */
-    /* FIXME: Check 1.3 below */
-    if ((version == OF_VERSION_1_2) || (version == OF_VERSION_1_3)) {
+    if ((version >= OF_VERSION_1_2)) {
         of_object_u16_set((of_object_t *)obj, %(match_offset)d + 2, 4);
     }
 """ % dict(match_offset=match_offset))
diff --git a/c_gen/c_dump_gen.py b/c_gen/c_dump_gen.py
index 2f41c35..4e7db7a 100644
--- a/c_gen/c_dump_gen.py
+++ b/c_gen/c_dump_gen.py
@@ -242,20 +242,20 @@
         out.write("};\n\n")
 
     out.write("""
-static const loci_obj_dump_f *const dump_funs[5] = {
-    NULL,
-    dump_funs_v1,
-    dump_funs_v2,
-    dump_funs_v3,
-    dump_funs_v4
+static const loci_obj_dump_f *const dump_funs[] = {
+""")
+
+    for version in of_g.of_version_range:
+        out.write("    [%(v)d] = dump_funs_v%(v)d,\n" % dict(v=version))
+
+    out.write("""\
 };
 
 int
 of_object_dump(loci_writer_f writer, void* cookie, of_object_t *obj)
 {
     if ((obj->object_id > 0) && (obj->object_id < OF_OBJECT_COUNT)) {
-        if (((obj)->version > 0) && ((obj)->version <= OF_VERSION_1_3)) {
-            /* @fixme VERSION */
+        if (OF_VERSION_OKAY(obj->version)) {
             return dump_funs[obj->version][obj->object_id](writer, cookie, (of_object_t *)obj);
         } else {
             return writer(cookie, "Bad version %d\\n", obj->version);
diff --git a/c_gen/c_match.py b/c_gen/c_match.py
index 3afc99c..e5a41ef 100644
--- a/c_gen/c_match.py
+++ b/c_gen/c_match.py
@@ -104,6 +104,13 @@
 #define of_match_v4_to_match of_match_v3_to_match
 #define of_match_to_wire_match_v4 of_match_to_wire_match_v3
 #define of_match_v4_delete of_match_v3_delete
+
+#define of_match_v5_t of_match_v3_t
+#define of_match_v5_init of_match_v3_init
+#define of_match_v5_new of_match_v3_new
+#define of_match_v5_to_match of_match_v3_to_match
+#define of_match_to_wire_match_v5 of_match_to_wire_match_v3
+#define of_match_v5_delete of_match_v3_delete
 """)
 
 def gen_match_macros(out):
diff --git a/c_gen/c_show_gen.py b/c_gen/c_show_gen.py
index f4d7945..9a13b43 100644
--- a/c_gen/c_show_gen.py
+++ b/c_gen/c_show_gen.py
@@ -311,20 +311,20 @@
         out.write("};\n\n")
 
     out.write("""
-static const loci_obj_show_f *const show_funs[5] = {
-    NULL,
-    show_funs_v1,
-    show_funs_v2,
-    show_funs_v3,
-    show_funs_v4
+static const loci_obj_show_f *const show_funs[] = {
+""")
+
+    for version in of_g.of_version_range:
+        out.write("    [%(v)d] = show_funs_v%(v)d,\n" % dict(v=version))
+
+    out.write("""\
 };
 
 int
 of_object_show(loci_writer_f writer, void* cookie, of_object_t *obj)
 {
     if ((obj->object_id > 0) && (obj->object_id < OF_OBJECT_COUNT)) {
-        if (((obj)->version > 0) && ((obj)->version <= OF_VERSION_1_2)) {
-            /* @fixme VERSION */
+        if (OF_VERSION_OKAY(obj->version)) {
             return show_funs[obj->version][obj->object_id](writer, cookie, (of_object_t *)obj);
         } else {
             return writer(cookie, "Bad version %d\\n", obj->version);
diff --git a/c_gen/c_test_gen.py b/c_gen/c_test_gen.py
index 41fe122..dacc8bf 100644
--- a/c_gen/c_test_gen.py
+++ b/c_gen/c_test_gen.py
@@ -541,7 +541,7 @@
     """
 
     members, member_types = scalar_member_types_get(cls, version)
-    length = of_g.base_length[(cls, version)] + of_g.extra_length.get((cls, version), 0)
+    length = of_g.base_length[(cls, version)]
     v_name = loxi_utils.version_to_name(version)
 
     out.write("""
@@ -773,13 +773,15 @@
     out.write("""
     %(base_type)s_t elt;
     int cur_len = 0;
+    (void) elt;
+    (void) cur_len;
 """ % dict(cls=cls, base_type=base_type))
 
     sub_classes =  type_maps.sub_class_map(base_type, version)
     sub_classes = [(instance, subcls) for (instance, subcls) in sub_classes if not type_maps.class_is_virtual(subcls)]
     v_name = loxi_utils.version_to_name(version)
 
-    if len(sub_classes) == 0:
+    if not type_maps.class_is_virtual(base_type):
         out.write("    /* No subclasses for %s */\n"% base_type)
         out.write("    %s_t *elt_p;\n" % base_type)
         out.write("\n    elt_p = &elt;\n")
@@ -791,7 +793,7 @@
         for instance, subcls in sub_classes:
             out.write("    %s = &elt.%s;\n" % (instance, instance))
 
-    if len(sub_classes) == 0: # No inheritance case
+    if not type_maps.class_is_virtual(base_type): # No inheritance case
         setup_instance(out, cls, base_type, "elt_p", v_name, version)
     else:
         for instance, subcls in sub_classes:
@@ -819,13 +821,14 @@
     base_type = loxi_utils.list_to_entry_type(cls)
     out.write("""
     %(base_type)s_t elt;
+    (void) elt;
 """ % dict(cls=cls, base_type=base_type))
 
     sub_classes =  type_maps.sub_class_map(base_type, version)
     sub_classes = [(instance, subcls) for (instance, subcls) in sub_classes if not type_maps.class_is_virtual(subcls)]
     v_name = loxi_utils.version_to_name(version)
 
-    if len(sub_classes) == 0:
+    if not type_maps.class_is_virtual(base_type):
         out.write("    /* No subclasses for %s */\n"% base_type)
         out.write("    %s_t *elt_p;\n" % base_type)
         out.write("\n    elt_p = &elt;\n")
@@ -837,8 +840,10 @@
         for instance, subcls in sub_classes:
             out.write("    %s = &elt.%s;\n" % (instance, instance))
 
-    out.write("    TEST_OK(%(cls)s_first(list, &elt));\n" % dict(cls=cls))
-    if len(sub_classes) == 0: # No inheritance case
+    if not type_maps.class_is_virtual(base_type) or sub_classes:
+        out.write("    TEST_OK(%(cls)s_first(list, &elt));\n" % dict(cls=cls))
+
+    if not type_maps.class_is_virtual(base_type): # No inheritance case
         check_instance(out, cls, base_type, "elt_p", v_name, version, True)
     else:
         count = 0
@@ -963,10 +968,12 @@
 static int
 test_match_1(void)
 {
-    of_match_v1_t *m_v1;
-    of_match_v2_t *m_v2;
-    of_match_v3_t *m_v3;
-    of_match_v4_t *m_v4;
+""")
+
+    for version in of_g.of_version_range:
+        out.write("    of_match_v%(v)d_t *m_v%(v)d;\n" % dict(v=version))
+
+    out.write("""\
     of_match_t match;
     int value = 1;
     int idx;
@@ -1001,10 +1008,12 @@
 static int
 test_match_2(void)
 {
-    of_match_v1_t *m_v1;
-    of_match_v2_t *m_v2;
-    of_match_v3_t *m_v3;
-    of_match_v3_t *m_v4;
+""")
+
+    for version in of_g.of_version_range:
+        out.write("    of_match_v%(v)d_t *m_v%(v)d;\n" % dict(v=version))
+
+    out.write("""\
     of_match_t match1;
     of_match_t match2;
     int value = 1;
@@ -1088,7 +1097,7 @@
                 continue
             if type_maps.class_is_virtual(cls):
                 continue
-            bytes = of_g.base_length[(cls, version)] + of_g.extra_length.get((cls, version), 0)
+            bytes = of_g.base_length[(cls, version)]
             out.write("""
 static int
 test_%(cls)s_create_%(v_name)s(void)
@@ -1173,13 +1182,15 @@
     out.write("""
     %(base_type)s_t elt;
     int cur_len = 0;
+    (void) elt;
+    (void) cur_len;
 """ % dict(cls=cls, base_type=base_type))
 
     sub_classes =  type_maps.sub_class_map(base_type, version)
     sub_classes = [(instance, subcls) for (instance, subcls) in sub_classes if not type_maps.class_is_virtual(subcls)]
     v_name = loxi_utils.version_to_name(version)
 
-    if len(sub_classes) == 0:
+    if not type_maps.class_is_virtual(base_type):
         out.write("    /* No subclasses for %s */\n"% base_type)
         out.write("    %s_t *elt_p;\n" % base_type)
         out.write("\n    elt_p = &elt;\n")
@@ -1191,7 +1202,7 @@
         for instance, subcls in sub_classes:
             out.write("    %s = &elt.%s;\n" % (instance, instance))
 
-    if len(sub_classes) == 0: # No inheritance case
+    if not type_maps.class_is_virtual(base_type): # No inheritance case
         setup_instance(out, cls, base_type, "elt_p", v_name, version)
     else:
         for instance, subcls in sub_classes:
@@ -1225,7 +1236,7 @@
     sub_classes = [(instance, subcls) for (instance, subcls) in sub_classes if not type_maps.class_is_virtual(subcls)]
     v_name = loxi_utils.version_to_name(version)
 
-    if len(sub_classes) == 0:
+    if not type_maps.class_is_virtual(base_type):
         entry_count = 2
         out.write("    /* No subclasses for %s */\n"% base_type)
         out.write("    %s_t *elt_p;\n" % base_type)
@@ -1239,8 +1250,10 @@
         for instance, subcls in sub_classes:
             out.write("    %s = &elt.%s;\n" % (instance, instance))
 
-    out.write("    TEST_OK(%(cls)s_first(list, &elt));\n" % dict(cls=cls))
-    if len(sub_classes) == 0: # No inheritance case
+    if not type_maps.class_is_virtual(base_type) or sub_classes:
+        out.write("    TEST_OK(%(cls)s_first(list, &elt));\n" % dict(cls=cls))
+
+    if not type_maps.class_is_virtual(base_type): # No inheritance case
         check_instance(out, cls, base_type, "elt_p", v_name,
                        version, True)
     else:
@@ -1518,7 +1531,7 @@
     """
 
     members, member_types = scalar_member_types_get(cls, version)
-    length = of_g.base_length[(cls, version)] + of_g.extra_length.get((cls, version), 0)
+    length = of_g.base_length[(cls, version)]
     v_name = loxi_utils.version_to_name(version)
 
     out.write("""
diff --git a/c_gen/c_type_maps.py b/c_gen/c_type_maps.py
index 6c45d4d..17f63ca 100644
--- a/c_gen/c_type_maps.py
+++ b/c_gen/c_type_maps.py
@@ -64,6 +64,15 @@
 extern void of_meter_stats_wire_length_get(of_object_t *obj, int *bytes);
 extern void of_meter_stats_wire_length_set(of_object_t *obj, int bytes);
 
+extern void of_port_desc_wire_length_get(of_object_t *obj, int *bytes);
+extern void of_port_desc_wire_length_set(of_object_t *obj, int bytes);
+
+extern void of_port_stats_entry_wire_length_get(of_object_t *obj, int *bytes);
+extern void of_port_stats_entry_wire_length_set(of_object_t *obj, int bytes);
+
+extern void of_queue_stats_entry_wire_length_get(of_object_t *obj, int *bytes);
+extern void of_queue_stats_entry_wire_length_set(of_object_t *obj, int bytes);
+
 """)
 
 
@@ -106,44 +115,3 @@
     out.write("""
 };
 """)
-
-
-def gen_extra_length_array(out):
-    """
-    Generate an array giving the extra lengths of all objects/versions
-    @param out The file handle to which to write
-    """
-    out.write("""
-/**
- * An array with the number of bytes in the extra length part
- * of each OF object
- */
-""")
-
-    for version in of_g.of_version_range:
-        out.write("""
-static const int\nof_object_extra_len_v%d[OF_OBJECT_COUNT] = {
-    -1,   /* of_object is not instantiable */
-""" % version)
-        for i, cls in enumerate(of_g.all_class_order):
-            comma = ","
-            if i == len(of_g.all_class_order) - 1:
-                comma = ""
-            val = "-1" + comma
-            if (cls, version) in of_g.base_length:
-                val = str(of_g.extra_length.get((cls, version), 0)) + comma
-            out.write("    %-5s /* %d: %s */\n" % (val, i + 1, cls))
-        out.write("};\n")
-
-    out.write("""
-/**
- * Unified map of extra length part of each object
- */
-const int *const of_object_extra_len[OF_VERSION_ARRAY_MAX] = {
-    NULL,
-""")
-    for version in of_g.of_version_range:
-        out.write("    of_object_extra_len_v%d,\n" % version)
-    out.write("""
-};
-""")
diff --git a/c_gen/codegen.py b/c_gen/codegen.py
index c1b00ad..dba3d53 100644
--- a/c_gen/codegen.py
+++ b/c_gen/codegen.py
@@ -186,7 +186,6 @@
     # Collect legacy code
     tmp = StringIO()
     c_type_maps.gen_length_array(tmp)
-    c_type_maps.gen_extra_length_array(tmp)
 
     with template_utils.open_output(install_dir, "loci/src/of_type_maps.c") as out:
         util.render_template(out, "of_type_maps.c", legacy_code=tmp.getvalue())
@@ -197,6 +196,13 @@
 class_metadata = []
 class_metadata_dict = {}
 
+# These classes have handwritten C code to get/set their length fields
+# See templates/of_type_maps.c
+special_length_classes = set([
+    'of_packet_queue', 'of_meter_stats', 'of_port_desc',
+    'of_port_stats_entry', 'of_queue_stats_entry',
+])
+
 def build_class_metadata():
     for uclass in loxi_globals.unified.classes:
         wire_length_get = 'NULL'
@@ -216,14 +222,9 @@
             wire_length_set = 'of_object_message_wire_length_set'
         elif uclass.is_oxm:
             wire_length_get = 'of_oxm_wire_length_get'
-        elif uclass.name == "of_packet_queue":
-            # u16 len, but at offset 4
-            wire_length_get = 'of_packet_queue_wire_length_get'
-            wire_length_set = 'of_packet_queue_wire_length_set'
-        elif uclass.name == "of_meter_stats":
-            # u16 len, but at offset 4
-            wire_length_get = 'of_meter_stats_wire_length_get'
-            wire_length_set = 'of_meter_stats_wire_length_set'
+        elif uclass.name in special_length_classes:
+            wire_length_get = '%s_wire_length_get' % uclass.name
+            wire_length_set = '%s_wire_length_set' % uclass.name
         elif loxi_utils_legacy.class_is_tlv16(uclass.name):
             wire_length_set = 'of_tlv16_wire_length_set'
             wire_length_get = 'of_tlv16_wire_length_get'
diff --git a/c_gen/match.py b/c_gen/match.py
index 7b7dcce..d6b43fa 100644
--- a/c_gen/match.py
+++ b/c_gen/match.py
@@ -131,6 +131,7 @@
     2: of_v2_keys,
     3: [],
     4: [],
+    5: [],
 }
 
 # Complete list of match keys, sorted by the standard order
diff --git a/c_gen/of_g_legacy.py b/c_gen/of_g_legacy.py
index 2372b31..87e0680 100644
--- a/c_gen/of_g_legacy.py
+++ b/c_gen/of_g_legacy.py
@@ -111,6 +111,7 @@
         2: "uint32_t",
         3: "uint32_t",
         4: "uint32_t",
+        5: "uint32_t",
         "short_name":"port_no"
         },
     of_port_desc_t = {
@@ -118,6 +119,7 @@
         2: "of_port_desc_t",
         3: "of_port_desc_t",
         4: "of_port_desc_t",
+        5: "of_port_desc_t",
         "short_name":"port_desc"
         },
     of_bsn_vport_t = {
@@ -125,6 +127,7 @@
         2: "of_bsn_vport_t",
         3: "of_bsn_vport_t",
         4: "of_bsn_vport_t",
+        5: "of_bsn_vport_t",
         "short_name":"bsn_vport"
         },
     of_fm_cmd_t = { # Flow mod command went from u16 to u8
@@ -132,6 +135,7 @@
         2: "uint8_t",
         3: "uint8_t",
         4: "uint8_t",
+        5: "uint8_t",
         "short_name":"fm_cmd"
         },
     of_wc_bmap_t = { # Wildcard bitmap
@@ -139,6 +143,7 @@
         2: "uint32_t",
         3: "uint64_t",
         4: "uint64_t",
+        5: "uint64_t",
         "short_name":"wc_bmap"
         },
     of_match_bmap_t = { # Match bitmap
@@ -146,6 +151,7 @@
         2: "uint32_t",
         3: "uint64_t",
         4: "uint64_t",
+        5: "uint64_t",
         "short_name":"match_bmap"
         },
     of_match_t = { # Match object
@@ -153,6 +159,7 @@
         2: "of_match_v2_t",
         3: "of_match_v3_t",
         4: "of_match_v3_t",  # Currently uses same match as 1.2 (v3).
+        5: "of_match_v3_t",  # Currently uses same match as 1.2 (v3).
         "short_name":"match"
         },
 )
@@ -266,12 +273,6 @@
 ## Map from class, wire_version to size of fixed part of class
 base_length = {}
 
-## Map from class, wire_version to size of variable-offset, fixed length part of class
-extra_length = {
-    ("of_packet_in", 3): 2,
-    ("of_packet_in", 4): 2,
-}
-
 ## Boolean indication of variable length, per class, wire_version,
 is_fixed_length = set()
 
diff --git a/c_gen/templates/loci_int.h b/c_gen/templates/loci_int.h
index 1ea9eeb..eb1c923 100644
--- a/c_gen/templates/loci_int.h
+++ b/c_gen/templates/loci_int.h
@@ -30,6 +30,8 @@
 :: flow_mod = of_g.base_length[("of_flow_modify",of_g.VERSION_1_2)]
 :: packet_in = of_g.base_length[("of_packet_in",of_g.VERSION_1_2)]
 :: packet_in_1_3 = of_g.base_length[("of_packet_in",of_g.VERSION_1_3)]
+:: packet_in_1_4 = of_g.base_length[("of_packet_in",of_g.VERSION_1_4)]
+:: assert packet_in_1_3 == packet_in_1_4
 :: flow_stats = of_g.base_length[("of_flow_stats_entry", of_g.VERSION_1_2)]
 :: match1 = of_g.base_length[("of_match_v1",of_g.VERSION_1_0)]
 :: match2 = of_g.base_length[("of_match_v2",of_g.VERSION_1_1)]
@@ -239,13 +241,16 @@
  * @param obj An object of type of_packet_in_t
  *
  * Get length of preceding match object and add to fixed length
- * Applies only to version 1.2 and 1.3
- * The +2 comes from the 2 bytes of padding between the match and packet data.
+ * Applies only to version 1.2+
+ * There are 2 bytes of padding between the match and data. The
+ * _OFFSET_FOLLOWING_MATCH_V3 macro assumes the match is at the end of the
+ * fixed length, so we need to subtract 2 from the fixed length we pass and
+ * then add 2 to the resulting offset.
  */
 
 #define _PACKET_IN_DATA_OFFSET(obj) \
     (_OFFSET_FOLLOWING_MATCH_V3((obj), (obj)->version == OF_VERSION_1_2 ? \
-${packet_in} : ${packet_in_1_3}) + 2)
+(${packet_in} - 2) : (${packet_in_1_3} - 2)) + 2)
 
 /**
  * Macro to calculate variable offset of data (packet) member in packet_out
diff --git a/c_gen/templates/loci_validator.c b/c_gen/templates/loci_validator.c
index 1f98b21..c4e8b5c 100644
--- a/c_gen/templates/loci_validator.c
+++ b/c_gen/templates/loci_validator.c
@@ -199,7 +199,7 @@
         return ${validator_name(proto.class_by_name('of_header'))}(msg, len, &out_len);
 :: #endfor
     default:
-        VALIDATOR_LOG("Bad version %d", OF_VERSION_1_3);
+        VALIDATOR_LOG("Bad version %d", version);
         return -1;
     }
 }
diff --git a/c_gen/templates/of_type_maps.c b/c_gen/templates/of_type_maps.c
index 9fa59e0..8847515 100644
--- a/c_gen/templates/of_type_maps.c
+++ b/c_gen/templates/of_type_maps.c
@@ -305,3 +305,141 @@
     of_wire_buffer_u16_set(wbuf, 
         OF_OBJECT_ABSOLUTE_OFFSET(obj, OF_METER_STATS_LENGTH_OFFSET), bytes);
 }
+
+/**
+ * Get the wire length for a port desc object
+ * @param obj The object being referenced
+ * @param bytes Pointer to location to store length
+ *
+ * The length is only present for OF 1.4+.
+ */
+void
+of_port_desc_wire_length_get(of_object_t *obj, int *bytes)
+{
+    of_wire_buffer_t *wbuf = OF_OBJECT_TO_WBUF(obj);
+    uint16_t u16;
+
+    LOCI_ASSERT(wbuf != NULL);
+
+    if (obj->version >= OF_VERSION_1_4) {
+        of_wire_buffer_u16_get(wbuf, OF_OBJECT_ABSOLUTE_OFFSET(obj, 4),
+                               &u16);
+        *bytes = u16;
+    } else {
+        *bytes = OF_OBJECT_FIXED_LENGTH(obj);
+    }
+}
+
+/**
+ * Set the wire length for a port desc object
+ * @param obj The object being referenced
+ * @param bytes The length of the object
+ *
+ * The length is only present for OF 1.4+.
+ */
+
+void
+of_port_desc_wire_length_set(of_object_t *obj, int bytes)
+{
+    of_wire_buffer_t *wbuf = OF_OBJECT_TO_WBUF(obj);
+    LOCI_ASSERT(wbuf != NULL);
+
+    if (obj->version >= OF_VERSION_1_4) {
+        of_wire_buffer_u16_set(wbuf, OF_OBJECT_ABSOLUTE_OFFSET(obj, 4),
+                               bytes);
+    } else {
+        LOCI_ASSERT(obj->length == OF_OBJECT_FIXED_LENGTH(obj));
+    }
+}
+
+/**
+ * Get the wire length for a port stats_entry object
+ * @param obj The object being referenced
+ * @param bytes Pointer to location to store length
+ *
+ * The length is only present for OF 1.4+.
+ */
+void
+of_port_stats_entry_wire_length_get(of_object_t *obj, int *bytes)
+{
+    of_wire_buffer_t *wbuf = OF_OBJECT_TO_WBUF(obj);
+    uint16_t u16;
+
+    LOCI_ASSERT(wbuf != NULL);
+
+    if (obj->version >= OF_VERSION_1_4) {
+        of_wire_buffer_u16_get(wbuf, OF_OBJECT_ABSOLUTE_OFFSET(obj, 0),
+                               &u16);
+        *bytes = u16;
+    } else {
+        *bytes = OF_OBJECT_FIXED_LENGTH(obj);
+    }
+}
+
+/**
+ * Set the wire length for a port stats_entry object
+ * @param obj The object being referenced
+ * @param bytes The length of the object
+ *
+ * The length is only present for OF 1.4+.
+ */
+
+void
+of_port_stats_entry_wire_length_set(of_object_t *obj, int bytes)
+{
+    of_wire_buffer_t *wbuf = OF_OBJECT_TO_WBUF(obj);
+    LOCI_ASSERT(wbuf != NULL);
+
+    if (obj->version >= OF_VERSION_1_4) {
+        of_wire_buffer_u16_set(wbuf, OF_OBJECT_ABSOLUTE_OFFSET(obj, 0),
+                               bytes);
+    } else {
+        LOCI_ASSERT(obj->length == OF_OBJECT_FIXED_LENGTH(obj));
+    }
+}
+
+/**
+ * Get the wire length for a queue stats_entry object
+ * @param obj The object being referenced
+ * @param bytes Pointer to location to store length
+ *
+ * The length is only present for OF 1.4+.
+ */
+void
+of_queue_stats_entry_wire_length_get(of_object_t *obj, int *bytes)
+{
+    of_wire_buffer_t *wbuf = OF_OBJECT_TO_WBUF(obj);
+    uint16_t u16;
+
+    LOCI_ASSERT(wbuf != NULL);
+
+    if (obj->version >= OF_VERSION_1_4) {
+        of_wire_buffer_u16_get(wbuf, OF_OBJECT_ABSOLUTE_OFFSET(obj, 0),
+                               &u16);
+        *bytes = u16;
+    } else {
+        *bytes = OF_OBJECT_FIXED_LENGTH(obj);
+    }
+}
+
+/**
+ * Set the wire length for a queue stats_entry object
+ * @param obj The object being referenced
+ * @param bytes The length of the object
+ *
+ * The length is only present for OF 1.4+.
+ */
+
+void
+of_queue_stats_entry_wire_length_set(of_object_t *obj, int bytes)
+{
+    of_wire_buffer_t *wbuf = OF_OBJECT_TO_WBUF(obj);
+    LOCI_ASSERT(wbuf != NULL);
+
+    if (obj->version >= OF_VERSION_1_4) {
+        of_wire_buffer_u16_set(wbuf, OF_OBJECT_ABSOLUTE_OFFSET(obj, 0),
+                               bytes);
+    } else {
+        LOCI_ASSERT(obj->length == OF_OBJECT_FIXED_LENGTH(obj));
+    }
+}
diff --git a/c_gen/templates/of_wire_buf.h b/c_gen/templates/of_wire_buf.h
index 0922a69..3d38f53 100644
--- a/c_gen/templates/of_wire_buf.h
+++ b/c_gen/templates/of_wire_buf.h
@@ -469,14 +469,10 @@
         of_wire_buffer_u16_get(wbuf, offset, &v16);
         *value = v16;
         break;
-    case OF_VERSION_1_1:
-    case OF_VERSION_1_2:
-    case OF_VERSION_1_3:
+    default:
         of_wire_buffer_u32_get(wbuf, offset, &v32);
         *value = v32;
         break;
-    default:
-        LOCI_ASSERT(0);
     }
 }
 
@@ -498,13 +494,9 @@
     case OF_VERSION_1_0:
         of_wire_buffer_u16_set(wbuf, offset, (uint16_t)value);
         break;
-    case OF_VERSION_1_1:
-    case OF_VERSION_1_2:
-    case OF_VERSION_1_3:
+    default:
         of_wire_buffer_u32_set(wbuf, offset, (uint32_t)value);
         break;
-    default:
-        LOCI_ASSERT(0);
     }
 }
 
@@ -527,14 +519,10 @@
         of_wire_buffer_u16_get(wbuf, offset, &v16);
         *value = v16;
         break;
-    case OF_VERSION_1_1:
-    case OF_VERSION_1_2:
-    case OF_VERSION_1_3:
+    default:
         of_wire_buffer_u8_get(wbuf, offset, &v8);
         *value = v8;
         break;
-    default:
-        LOCI_ASSERT(0);
     }
 }
 
@@ -553,13 +541,9 @@
     case OF_VERSION_1_0:
         of_wire_buffer_u16_set(wbuf, offset, (uint16_t)value);
         break;
-    case OF_VERSION_1_1:
-    case OF_VERSION_1_2:
-    case OF_VERSION_1_3:
+    default:
         of_wire_buffer_u8_set(wbuf, offset, (uint8_t)value);
         break;
-    default:
-        LOCI_ASSERT(0);
     }
 }
 
@@ -583,13 +567,10 @@
         of_wire_buffer_u32_get(wbuf, offset, &v32);
         *value = v32;
         break;
-    case OF_VERSION_1_2:
-    case OF_VERSION_1_3:
+    default:
         of_wire_buffer_u64_get(wbuf, offset, &v64);
         *value = v64;
         break;
-    default:
-        LOCI_ASSERT(0);
     }
 }
 
@@ -609,12 +590,9 @@
     case OF_VERSION_1_1:
         of_wire_buffer_u32_set(wbuf, offset, (uint32_t)value);
         break;
-    case OF_VERSION_1_2:
-    case OF_VERSION_1_3:
+    default:
         of_wire_buffer_u64_set(wbuf, offset, (uint64_t)value);
         break;
-    default:
-        LOCI_ASSERT(0);
     }
 }
 
diff --git a/loxi_ir/ir_offset.py b/loxi_ir/ir_offset.py
index c65cfed..db55f24 100644
--- a/loxi_ir/ir_offset.py
+++ b/loxi_ir/ir_offset.py
@@ -187,6 +187,8 @@
         member_ir_class = existing_classes[base_class]
         bytes = member_ir_class.base_length
         length_fixed = member_ir_class.is_fixed_length
+        if member_ir_class.has_external_alignment:
+            bytes = (bytes + 7) & ~7
     else:
         if base_type in existing_enums:
             enum = existing_enums[base_type]
diff --git a/openflow_input/standard-1.4 b/openflow_input/standard-1.4
index 2f495ed..9b923af 100644
--- a/openflow_input/standard-1.4
+++ b/openflow_input/standard-1.4
@@ -832,6 +832,22 @@
     uint16_t length; /* Length in bytes of this property. */
 };
 
+struct of_port_mod_prop_ethernet : of_port_mod_prop {
+    uint16_t type == 0;
+    uint16_t length;
+    uint32_t advertise;
+};
+
+struct of_port_mod_prop_optical : of_port_mod_prop {
+    uint16_t type == 1;
+    uint16_t length;
+    uint32_t configure;
+    uint32_t freq_ldma;
+    uint32_t fl_offset; /* TODO signed */
+    uint32_t grid_span;
+    uint32_t tx_pwr;
+};
+
 struct of_port_mod : of_header {
     uint8_t version;
     uint8_t type == 16;
diff --git a/test_data/of13/port_status.data b/test_data/of13/port_status.data
index 10f7c16..e56705f 100644
--- a/test_data/of13/port_status.data
+++ b/test_data/of13/port_status.data
@@ -34,3 +34,24 @@
         peer=ofp.OFPPF_100MB_FD,
         curr_speed=10,
         max_speed=20))
+-- c
+obj = of_port_status_new(OF_VERSION_1_3);
+of_port_status_xid_set(obj, 0x12345678);
+of_port_status_reason_set(obj, OF_PORT_CHANGE_REASON_MODIFY);
+{
+    of_object_t desc;
+    of_port_status_desc_bind(obj, &desc);
+    of_port_desc_port_no_set(&desc, 4);
+    of_mac_addr_t hw_addr = { { 1, 2, 3, 4, 5, 6 } };
+    of_port_desc_hw_addr_set(&desc, hw_addr);
+    of_port_name_t name = "foo";
+    of_port_desc_name_set(&desc, name);
+    of_port_desc_config_set(&desc, OF_PORT_CONFIG_FLAG_NO_FWD|OF_PORT_CONFIG_FLAG_NO_RECV);
+    of_port_desc_state_set(&desc, OF_PORT_STATE_FLAG_BLOCKED);
+    of_port_desc_curr_set(&desc, OF_PORT_FEATURE_FLAG_10MB_HD);
+    of_port_desc_advertised_set(&desc, OF_PORT_FEATURE_FLAG_10MB_FD);
+    of_port_desc_supported_set(&desc, OF_PORT_FEATURE_FLAG_100MB_HD);
+    of_port_desc_peer_set(&desc, OF_PORT_FEATURE_FLAG_100MB_FD);
+    of_port_desc_curr_speed_set(&desc, 10);
+    of_port_desc_max_speed_set(&desc, 20);
+}
diff --git a/test_data/of14/port_stats_reply.data b/test_data/of14/port_stats_reply.data
index 43dc471..0ff4902 100644
--- a/test_data/of14/port_stats_reply.data
+++ b/test_data/of14/port_stats_reply.data
@@ -65,7 +65,23 @@
         of_port_stats_entry_tx_dropped_set(obj, 0);
         of_port_stats_entry_rx_errors_set(obj, 0);
         of_port_stats_entry_tx_errors_set(obj, 2);
-        /* TODO append property */
+
+        /* Append property */
+        {
+            of_object_t list;
+            of_port_stats_entry_properties_bind(obj, &list);
+            {
+                of_object_t *obj = of_port_stats_prop_ethernet_new(OF_VERSION_1_4);
+                of_port_stats_prop_ethernet_rx_frame_err_set(obj, 1);
+                of_port_stats_prop_ethernet_rx_over_err_set(obj, 2);
+                of_port_stats_prop_ethernet_rx_crc_err_set(obj, 3);
+                of_port_stats_prop_ethernet_collisions_set(obj, 4);
+                of_list_append(&list, obj);
+                of_object_delete(obj);
+            }
+        }
+
+
         of_list_append(&list, obj);
         of_object_delete(obj);
     }
diff --git a/test_data/of14/port_status.data b/test_data/of14/port_status.data
new file mode 100644
index 0000000..9fb5bde
--- /dev/null
+++ b/test_data/of14/port_status.data
@@ -0,0 +1,72 @@
+-- binary
+05 0c # version, type
+00 58 # length
+12 34 56 78 # xid
+02 # reason
+00 00 00 00 00 00 00 # pad
+00 00 00 04 # port_no
+00 48 # length
+00 00 # pad
+01 02 03 04 05 06 # hw_addr
+00 00 # pad
+66 6f 6f 00 00 00 00 00 # name
+00 00 00 00 00 00 00 00 # ...
+00 00 00 24 # config
+00 00 00 02 # state
+00 00 # properties[0].type (ethernet)
+00 20 # properties[0].length
+00 00 00 00 # pad
+00 00 00 01 # properties[0].curr
+00 00 00 02 # properties[0].advertised
+00 00 00 04 # properties[0].supported
+00 00 00 08 # properties[0].peer
+00 00 00 0a # properties[0].curr_speed
+00 00 00 14 # properties[0].max_speed
+-- python
+ofp.message.port_status(
+    xid=0x12345678,
+    reason=ofp.OFPPR_MODIFY,
+    desc=ofp.port_desc(
+        port_no=4,
+        hw_addr=[1,2,3,4,5,6],
+        name="foo",
+        config=ofp.OFPPC_NO_FWD|ofp.OFPPC_NO_RECV,
+        state=ofp.OFPPS_BLOCKED,
+        properties=[
+            ofp.port_desc_prop.ethernet(
+                curr=ofp.OFPPF_10MB_HD,
+                advertised=ofp.OFPPF_10MB_FD,
+                supported=ofp.OFPPF_100MB_HD,
+                peer=ofp.OFPPF_100MB_FD,
+                curr_speed=10,
+                max_speed=20)]))
+-- c
+obj = of_port_status_new(OF_VERSION_1_4);
+of_port_status_xid_set(obj, 0x12345678);
+of_port_status_reason_set(obj, OF_PORT_CHANGE_REASON_MODIFY);
+{
+    of_object_t desc;
+    of_port_status_desc_bind(obj, &desc);
+    of_port_desc_port_no_set(&desc, 4);
+    of_mac_addr_t hw_addr = { { 1, 2, 3, 4, 5, 6 } };
+    of_port_desc_hw_addr_set(&desc, hw_addr);
+    of_port_name_t name = "foo";
+    of_port_desc_name_set(&desc, name);
+    of_port_desc_config_set(&desc, OF_PORT_CONFIG_FLAG_NO_FWD|OF_PORT_CONFIG_FLAG_NO_RECV);
+    of_port_desc_state_set(&desc, OF_PORT_STATE_FLAG_BLOCKED);
+    {
+        of_list_port_desc_prop_t list;
+        of_port_desc_properties_bind(&desc, &list);
+        {
+            of_object_t *obj = of_port_desc_prop_ethernet_new(OF_VERSION_1_4);
+            of_port_desc_prop_ethernet_curr_set(obj, OF_PORT_FEATURE_FLAG_10MB_HD);
+            of_port_desc_prop_ethernet_advertised_set(obj, OF_PORT_FEATURE_FLAG_10MB_FD);
+            of_port_desc_prop_ethernet_supported_set(obj, OF_PORT_FEATURE_FLAG_100MB_HD);
+            of_port_desc_prop_ethernet_peer_set(obj, OF_PORT_FEATURE_FLAG_100MB_FD);
+            of_port_desc_prop_ethernet_curr_speed_set(obj, 10);
+            of_port_desc_prop_ethernet_max_speed_set(obj, 20);
+            of_list_append(&list, obj);
+            of_object_delete(obj);
+        }
+    }
+}