pyloxi: deserialize OXM lists
diff --git a/py_gen/oftype.py b/py_gen/oftype.py
index 44356e2..0c925fa 100644
--- a/py_gen/oftype.py
+++ b/py_gen/oftype.py
@@ -125,8 +125,8 @@
         elif self.base == 'of_list_hello_elem_t':
             return 'common.unpack_list_hello_elem(%s)' % (reader_expr)
         elif self.base == 'of_list_oxm_t':
-            # TODO
-            return '[]'
+            # HACK need the match_v3 length field
+            return 'oxm.unpack_list(%s.slice(_length-4))' % (reader_expr)
         elif self.base == 'of_port_name_t':
             return self._gen_string_unpack_expr(reader_expr, 16)
         elif self.base == 'of_table_name_t' or self.base == 'of_serial_num_t':
diff --git a/py_gen/templates/common.py b/py_gen/templates/common.py
index 75c6f4c..19a0d9b 100644
--- a/py_gen/templates/common.py
+++ b/py_gen/templates/common.py
@@ -36,6 +36,10 @@
 import util
 import loxi.generic_util
 
+:: if version >= 3:
+import oxm
+:: #endif
+
 # HACK make this module visible as 'common' to simplify code generation
 common = sys.modules[__name__]
 
@@ -64,10 +68,6 @@
             return None
     return [x for x in loxi.generic_util.unpack_list_tlv16(reader, deserializer) if x != None]
 
-def unpack_list_oxm(buf):
-    # TODO
-    return []
-
 :: for ofclass in ofclasses:
 :: include('_ofclass.py', ofclass=ofclass, superclass="object")
 
diff --git a/py_gen/templates/oxm.py b/py_gen/templates/oxm.py
index 66886e0..5d5df7a 100644
--- a/py_gen/templates/oxm.py
+++ b/py_gen/templates/oxm.py
@@ -37,6 +37,16 @@
 import loxi.generic_util
 import loxi
 
+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)
+
 class OXM(object):
     type_len = None # override in subclass
     pass
diff --git a/py_gen/tests/of12.py b/py_gen/tests/of12.py
index b0dec92..0a2f38e 100644
--- a/py_gen/tests/of12.py
+++ b/py_gen/tests/of12.py
@@ -71,6 +71,42 @@
         obj = ofp.match.unpack(self.sample_empty_match_buf)
         self.assertEquals(len(obj.oxm_list), 0)
 
+    sample_match_buf = ''.join([
+        '\x00\x01', # type
+        '\x00\x3C', # length
+        '\x80\x00', # oxm_list[0].class
+        '\x20\x02', # oxm_list[0].type_len
+        '\x00\x35', # oxm_list[0].value
+        '\x80\x00', # oxm_list[1].class
+        '\x05\x10', # oxm_list[1].type_len
+        '\xFE\xDC\xBA\x98\x76\x54\x32\x10', # oxm_list[1].value
+        '\xFF\xFF\xFF\xFF\x12\x34\x56\x78', # oxm_list[1].mask
+        '\x80\x00', # oxm_list[2].class
+        '\x08\x06', # oxm_list[2].type_len
+        '\x01\x02\x03\x04\x05\x06', # oxm_list[2].value
+        '\x80\x00', # oxm_list[3].class
+        '\x36\x10', # oxm_list[3].type_len
+        '\x12' * 16, # oxm_list[3].value
+        '\x00' * 4, # padding
+    ])
+
+    def test_match_pack(self):
+        obj = ofp.match([
+            ofp.oxm.udp_dst(53),
+            ofp.oxm.metadata_masked(0xFEDCBA9876543210, 0xFFFFFFFF12345678),
+            ofp.oxm.eth_src([1,2,3,4,5,6]),
+            ofp.oxm.ipv6_dst("\x12" * 16),
+        ])
+        self.assertEquals(self.sample_match_buf, obj.pack())
+
+    def test_match_unpack(self):
+        obj = ofp.match.unpack(self.sample_match_buf)
+        self.assertEquals(len(obj.oxm_list), 4)
+        self.assertEquals(obj.oxm_list[0], ofp.oxm.udp_dst(53))
+        self.assertEquals(obj.oxm_list[1], ofp.oxm.metadata_masked(0xFEDCBA9876543210, 0xFFFFFFFF12345678))
+        self.assertEquals(obj.oxm_list[2], ofp.oxm.eth_src([1,2,3,4,5,6]))
+        self.assertEquals(obj.oxm_list[3], ofp.oxm.ipv6_dst("\x12" * 16))
+
 class TestOXM(unittest.TestCase):
     def test_oxm_in_phy_port_pack(self):
         import loxi.of12 as ofp