pyloxi: support OXM member in set-field actions

The C backend needs more work to support of_oxm_t, so I've added a hack so it
can keep using of_octets_t.

Also added several set-field action testcases.
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/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..b6e8ef7 100644
--- a/py_gen/templates/_pack.py
+++ b/py_gen/templates/_pack.py
@@ -56,6 +56,11 @@
 :: #endfor
 :: if length_member_index != None:
         length = sum([len(x) for x in packed])
+:: if ofclass.name == 'of_action_set_field':
+        pad_len = (length + 7)/8*8 - length
+        length += pad_len
+        packed.append('\x00' * pad_len)
+:: #endif
         packed[${length_member_index}] = ${gen_pack_expr(length_member.oftype, 'length')}
 :: #endif
 :: if ofclass.name == 'of_match_v3':
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/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))