Merge into master from pull request #36:
implement OF 1.3 set-field actions in pyloxi (https://github.com/floodlight/loxigen/pull/36)
diff --git a/loxigen.py b/loxigen.py
index 1d1f245..36d6872 100755
--- a/loxigen.py
+++ b/loxigen.py
@@ -442,7 +442,12 @@
                                                    name=m_name))
                         pad_count += 1
                     else:
-                        legacy_members.append(dict(m_type=m.oftype, name=m.name))
+                        # HACK the C backend does not yet support of_oxm_t
+                        if m.oftype == 'of_oxm_t':
+                            m_type = 'of_octets_t'
+                        else:
+                            m_type = m.oftype
+                        legacy_members.append(dict(m_type=m_type, name=m.name))
                 versions[version_name]['classes'][ofclass.name] = legacy_members
 
             for enum in ofinput.enums:
diff --git a/openflow_input/standard-1.2 b/openflow_input/standard-1.2
index 4650e06..8b4fa3f 100644
--- a/openflow_input/standard-1.2
+++ b/openflow_input/standard-1.2
@@ -692,7 +692,7 @@
 struct of_action_set_field {
     uint16_t type == 25;
     uint16_t len;
-    of_octets_t field;
+    of_oxm_t field;
 };
 
 struct of_action_experimenter {
diff --git a/openflow_input/standard-1.3 b/openflow_input/standard-1.3
index ccef4db..0d5cec3 100644
--- a/openflow_input/standard-1.3
+++ b/openflow_input/standard-1.3
@@ -824,7 +824,7 @@
 struct of_action_set_field {
     uint16_t type == 25;
     uint16_t len;
-    of_octets_t field;
+    of_oxm_t field;
 };
 
 struct of_action_experimenter {
diff --git a/py_gen/codegen.py b/py_gen/codegen.py
index 4e93c4c..6570938 100644
--- a/py_gen/codegen.py
+++ b/py_gen/codegen.py
@@ -35,7 +35,8 @@
 from loxi_ir import *
 
 PyOFClass = namedtuple('PyOFClass', ['name', 'pyname', 'members', 'type_members',
-                                     'min_length', 'is_fixed_length'])
+                                     'min_length', 'is_fixed_length',
+                                     'has_internal_alignment', 'has_external_alignment'])
 
 # Return the name for the generated Python class
 def generate_pyname(cls):
@@ -92,7 +93,9 @@
                       members=members,
                       type_members=type_members,
                       min_length=of_g.base_length[(cls, version)],
-                      is_fixed_length=(cls, version) in of_g.is_fixed_length))
+                      is_fixed_length=(cls, version) in of_g.is_fixed_length,
+                      has_internal_alignment=cls == 'of_action_set_field',
+                      has_external_alignment=cls == 'of_match_v3'))
     return ofclasses
 
 def generate_init(out, name, version):
diff --git a/py_gen/oftype.py b/py_gen/oftype.py
index cf87ded..67af1e7 100644
--- a/py_gen/oftype.py
+++ b/py_gen/oftype.py
@@ -99,6 +99,11 @@
         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='[]',
diff --git a/py_gen/templates/_pack.py b/py_gen/templates/_pack.py
index 4714e97..9956cf6 100644
--- a/py_gen/templates/_pack.py
+++ b/py_gen/templates/_pack.py
@@ -56,8 +56,12 @@
 :: #endfor
 :: if length_member_index != None:
         length = sum([len(x) for x in packed])
+:: if ofclass.has_internal_alignment:
+        packed.append(loxi.generic_util.pad_to(8, length))
+        length += len(packed[-1])
+:: #endif
         packed[${length_member_index}] = ${gen_pack_expr(length_member.oftype, 'length')}
 :: #endif
-:: if ofclass.name == 'of_match_v3':
-        packed.append('\x00' * ((length + 7)/8*8 - length))
+:: if ofclass.has_external_alignment:
+        packed.append(loxi.generic_util.pad_to(8, length))
 :: #endif
diff --git a/py_gen/templates/_unpack.py b/py_gen/templates/_unpack.py
index 133e831..09a0e08 100644
--- a/py_gen/templates/_unpack.py
+++ b/py_gen/templates/_unpack.py
@@ -53,6 +53,6 @@
         obj.${m.name} = ${gen_unpack_expr(m.oftype, reader_expr)}
 ::     #endif
 :: #endfor
-:: if ofclass.name == 'of_match_v3':
-        reader.skip((_length + 7)/8*8 - _length)
+:: if ofclass.has_external_alignment or ofclass.has_internal_alignment:
+        reader.skip_align()
 :: #endif
diff --git a/py_gen/templates/action.py b/py_gen/templates/action.py
index dccb46e..fba5294 100644
--- a/py_gen/templates/action.py
+++ b/py_gen/templates/action.py
@@ -37,6 +37,9 @@
 import util
 import loxi.generic_util
 import loxi
+:: if version >= 3:
+import oxm # for unpack
+:: #endif
 
 def unpack_list(reader):
     def deserializer(reader, typ):
diff --git a/py_gen/templates/generic_util.py b/py_gen/templates/generic_util.py
index 53091ed..28d3b5f 100644
--- a/py_gen/templates/generic_util.py
+++ b/py_gen/templates/generic_util.py
@@ -63,6 +63,13 @@
         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
+    a multiple of 'alignment'.
+    """
+    return "\x00" * ((length + alignment - 1)/alignment*alignment - length)
+
 class OFReader(object):
     """
     Cursor over a read-only buffer
@@ -102,6 +109,12 @@
             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):
+            raise loxi.ProtocolError("Buffer too short")
+        self.offset = new_offset
+
     def is_empty(self):
         return self.offset == len(self.buf)
 
diff --git a/py_gen/templates/oxm.py b/py_gen/templates/oxm.py
index d95dd8e..792c277 100644
--- a/py_gen/templates/oxm.py
+++ b/py_gen/templates/oxm.py
@@ -38,15 +38,16 @@
 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):
-    def deserializer(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))
-    return loxi.generic_util.unpack_list(reader, deserializer)
+    return loxi.generic_util.unpack_list(reader, unpack)
 
 class OXM(object):
     type_len = None # override in subclass
diff --git a/py_gen/tests/of12.py b/py_gen/tests/of12.py
index c6c94ab..98d999b 100644
--- a/py_gen/tests/of12.py
+++ b/py_gen/tests/of12.py
@@ -78,7 +78,9 @@
         self.klasses.sort(key=lambda x: str(x))
 
     def test_serialization(self):
-        expected_failures = []
+        expected_failures = [
+            ofp.action.set_field, # field defaults to None
+        ]
         for klass in self.klasses:
             def fn():
                 obj = klass()
diff --git a/py_gen/tests/of13.py b/py_gen/tests/of13.py
index 349881f..8c18f41 100644
--- a/py_gen/tests/of13.py
+++ b/py_gen/tests/of13.py
@@ -93,6 +93,7 @@
 
     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,
diff --git a/test_data/of13/action_output.data b/test_data/of13/action_output.data
new file mode 100644
index 0000000..7cd52ce
--- /dev/null
+++ b/test_data/of13/action_output.data
@@ -0,0 +1,8 @@
+-- binary
+00 00 # type
+00 10 # length
+00 00 00 32 # port
+ff ff # max_len
+00 00 00 00 00 00 # pad
+-- python
+ofp.action.output(port=50, max_len=65535)
diff --git a/test_data/of13/action_set_field_eth_dst.data b/test_data/of13/action_set_field_eth_dst.data
new file mode 100644
index 0000000..1e4a971
--- /dev/null
+++ b/test_data/of13/action_set_field_eth_dst.data
@@ -0,0 +1,8 @@
+-- binary
+00 19 # type
+00 10 # length
+80 00 06 06 # OXM header
+00 01 02 03 04 05 # OXM value
+00 00 # pad
+-- python
+ofp.action.set_field(field=ofp.oxm.eth_dst([0, 1, 2, 3, 4, 5]))
diff --git a/test_data/of13/action_set_field_ipv6_src.data b/test_data/of13/action_set_field_ipv6_src.data
new file mode 100644
index 0000000..f440dee
--- /dev/null
+++ b/test_data/of13/action_set_field_ipv6_src.data
@@ -0,0 +1,7 @@
+-- binary
+00 19 # type
+00 18 # length
+80 00 34 10 # OXM header
+00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f # OXM value
+-- python
+ofp.action.set_field(field=ofp.oxm.ipv6_src("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"))
diff --git a/test_data/of13/action_set_field_tcp_src.data b/test_data/of13/action_set_field_tcp_src.data
new file mode 100644
index 0000000..a69c7c0
--- /dev/null
+++ b/test_data/of13/action_set_field_tcp_src.data
@@ -0,0 +1,8 @@
+-- binary
+00 19 # type
+00 10 # length
+80 00 1a 02 # OXM header
+00 32 # OXM value
+00 00 00 00 00 00 # pad
+-- python
+ofp.action.set_field(field=ofp.oxm.tcp_src(50))