pyloxi: factor out generic list parsing code
diff --git a/py_gen/templates/action.py b/py_gen/templates/action.py
index 439e56f..8f08ced 100644
--- a/py_gen/templates/action.py
+++ b/py_gen/templates/action.py
@@ -37,18 +37,13 @@
 
 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
diff --git a/py_gen/templates/common.py b/py_gen/templates/common.py
index 08fb65d..fbcec25 100644
--- a/py_gen/templates/common.py
+++ b/py_gen/templates/common.py
@@ -39,41 +39,19 @@
 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):
diff --git a/py_gen/templates/util.py b/py_gen/templates/util.py
index 5933e14..bfe11d0 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,25 @@
     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)
+    while offset < len(buf):
+        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.py
index 23d40b8..68f8b52 100644
--- a/py_gen/tests.py
+++ b/py_gen/tests.py
@@ -153,7 +153,7 @@
         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'