pyloxi: rewrite deserialization to use OFReader
diff --git a/py_gen/templates/_unpack.py b/py_gen/templates/_unpack.py
index 91186cc..8be8254 100644
--- a/py_gen/templates/_unpack.py
+++ b/py_gen/templates/_unpack.py
@@ -27,22 +27,21 @@
::
:: # TODO coalesce format strings
:: from py_gen.codegen import Member, LengthMember, TypeMember, PadMember
+ if type(buf) == loxi.generic_util.OFReader:
+ reader = buf
+ else:
+ reader = loxi.generic_util.OFReader(buf)
:: for m in ofclass.members:
:: if type(m) == PadMember:
+ reader.skip(${m.length})
:: continue
:: #endif
-:: unpack_expr = m.oftype.gen_unpack_expr('buf', m.offset)
+:: unpack_expr = m.oftype.gen_unpack_expr('reader')
:: if type(m) == LengthMember:
- _length = ${unpack_expr}
- assert(_length == len(buf))
-:: if ofclass.is_fixed_length:
- if _length != ${ofclass.min_length}: raise loxi.ProtocolError("${ofclass.pyname} length is %d, should be ${ofclass.min_length}" % _length)
-:: else:
- if _length < ${ofclass.min_length}: raise loxi.ProtocolError("${ofclass.pyname} length is %d, should be at least ${ofclass.min_length}" % _length)
-:: #endif
+ _${m.name} = ${unpack_expr}
:: elif type(m) == TypeMember:
- ${m.name} = ${unpack_expr}
- assert(${m.name} == ${m.value})
+ _${m.name} = ${unpack_expr}
+ assert(_${m.name} == ${m.value})
:: else:
obj.${m.name} = ${unpack_expr}
:: #endif
diff --git a/py_gen/templates/_unpack_packet_out.py b/py_gen/templates/_unpack_packet_out.py
index b97e829..f7f9683 100644
--- a/py_gen/templates/_unpack_packet_out.py
+++ b/py_gen/templates/_unpack_packet_out.py
@@ -25,16 +25,18 @@
:: # EPL for the specific language governing permissions and limitations
:: # under the EPL.
::
- version = struct.unpack_from('!B', buf, 0)[0]
- assert(version == const.OFP_VERSION)
- type = struct.unpack_from('!B', buf, 1)[0]
- assert(type == const.OFPT_PACKET_OUT)
- _length = struct.unpack_from('!H', buf, 2)[0]
- assert(_length == len(buf))
- if _length < 16: raise loxi.ProtocolError("packet_out length is %d, should be at least 16" % _length)
- obj.xid = struct.unpack_from('!L', buf, 4)[0]
- obj.buffer_id = struct.unpack_from('!L', buf, 8)[0]
- obj.in_port = struct.unpack_from('!H', buf, 12)[0]
- actions_len = struct.unpack_from('!H', buf, 14)[0]
- obj.actions = action.unpack_list(buffer(buf, 16, actions_len))
- obj.data = str(buffer(buf, 16+actions_len))
+ if type(buf) == loxi.generic_util.OFReader:
+ reader = buf
+ else:
+ reader = loxi.generic_util.OFReader(buf)
+ _version = reader.read('!B')[0]
+ assert(_version == const.OFP_VERSION)
+ _type = reader.read('!B')[0]
+ assert(_type == const.OFPT_PACKET_OUT)
+ _length = reader.read('!H')[0]
+ obj.xid = reader.read('!L')[0]
+ obj.buffer_id = reader.read('!L')[0]
+ obj.in_port = reader.read('!H')[0]
+ _actions_len = reader.read('!H')[0]
+ obj.actions = action.unpack_list(reader.slice(_actions_len))
+ obj.data = str(reader.read_all())
diff --git a/py_gen/templates/action.py b/py_gen/templates/action.py
index 1c7a75a..d8f7c80 100644
--- a/py_gen/templates/action.py
+++ b/py_gen/templates/action.py
@@ -37,15 +37,12 @@
import loxi.generic_util
import loxi
-def unpack_list(buf):
- if len(buf) % 8 != 0: raise loxi.ProtocolError("action list length not a multiple of 8")
- def deserializer(buf):
- type, length = struct.unpack_from("!HH", buf)
- if length % 8 != 0: raise loxi.ProtocolError("action length not a multiple of 8")
- parser = parsers.get(type)
- if not parser: raise loxi.ProtocolError("unknown action type %d" % type)
- return parser(buf)
- return loxi.generic_util.unpack_list(deserializer, "!2xH", buf)
+def unpack_list(reader):
+ def deserializer(reader, typ):
+ parser = parsers.get(typ)
+ if not parser: raise loxi.ProtocolError("unknown action type %d" % typ)
+ return parser(reader)
+ return loxi.generic_util.unpack_list_tlv16(reader, deserializer)
class Action(object):
type = None # override in subclass
@@ -57,23 +54,21 @@
:: #endfor
:: if version == of_g.VERSION_1_0:
-def parse_vendor(buf):
+def parse_vendor(reader):
:: else:
-def parse_experimenter(buf):
+def parse_experimenter(reader):
:: #endif
- if len(buf) < 16:
- raise loxi.ProtocolError("experimenter action too short")
- experimenter, = struct.unpack_from("!L", buf, 4)
+ experimenter, = reader.peek("!4xL")
if experimenter == 0x005c16c7: # Big Switch Networks
- subtype, = struct.unpack_from("!L", buf, 8)
+ subtype, = reader.peek("!8xL")
elif experimenter == 0x00002320: # Nicira
- subtype, = struct.unpack_from("!H", buf, 8)
+ subtype, = reader.peek("!8xH")
else:
raise loxi.ProtocolError("unexpected experimenter id %#x" % experimenter)
if subtype in experimenter_parsers[experimenter]:
- return experimenter_parsers[experimenter][subtype](buf)
+ return experimenter_parsers[experimenter][subtype](reader)
else:
raise loxi.ProtocolError("unexpected BSN experimenter subtype %#x" % subtype)
diff --git a/py_gen/templates/common.py b/py_gen/templates/common.py
index 516bf05..ccf6032 100644
--- a/py_gen/templates/common.py
+++ b/py_gen/templates/common.py
@@ -39,29 +39,30 @@
# HACK make this module visible as 'common' to simplify code generation
common = sys.modules[__name__]
-def unpack_list_flow_stats_entry(buf):
- return loxi.generic_util.unpack_list(flow_stats_entry.unpack, "!H", buf)
+def unpack_list_flow_stats_entry(reader):
+ return loxi.generic_util.unpack_list_lv16(reader, flow_stats_entry.unpack)
-def unpack_list_queue_prop(buf):
- def deserializer(buf):
- type, = struct.unpack_from("!H", buf)
- if type == const.OFPQT_MIN_RATE:
- return queue_prop_min_rate.unpack(buf)
+def unpack_list_queue_prop(reader):
+ def deserializer(reader, typ):
+ if typ == const.OFPQT_MIN_RATE:
+ return queue_prop_min_rate.unpack(reader)
else:
- raise loxi.ProtocolError("unknown queue prop %d" % type)
- return loxi.generic_util.unpack_list(deserializer, "!2xH", buf)
+ raise loxi.ProtocolError("unknown queue prop %d" % typ)
+ return loxi.generic_util.unpack_list_tlv16(reader, deserializer)
-def unpack_list_packet_queue(buf):
- return loxi.generic_util.unpack_list(packet_queue.unpack, "!4xH", buf)
+def unpack_list_packet_queue(reader):
+ def wrapper(reader):
+ length, = reader.peek('!4xH')
+ return packet_queue.unpack(reader.slice(length))
+ return loxi.generic_util.unpack_list(reader, wrapper)
-def unpack_list_hello_elem(buf):
- def deserializer(buf):
- type, = struct.unpack_from("!H", buf)
- if type == const.OFPHET_VERSIONBITMAP:
- return hello_elem_versionbitmap.unpack(buf)
+def unpack_list_hello_elem(reader):
+ def deserializer(reader, typ):
+ if typ == const.OFPHET_VERSIONBITMAP:
+ return hello_elem_versionbitmap.unpack(reader)
else:
return None
- return [x for x in loxi.generic_util.unpack_list(deserializer, "!2xH", buf) if x != None]
+ return [x for x in loxi.generic_util.unpack_list_tlv16(reader, deserializer) if x != None]
:: for ofclass in ofclasses:
:: include('_ofclass.py', ofclass=ofclass, superclass="object")
diff --git a/py_gen/templates/generic_util.py b/py_gen/templates/generic_util.py
index a69cd62..53091ed 100644
--- a/py_gen/templates/generic_util.py
+++ b/py_gen/templates/generic_util.py
@@ -35,36 +35,34 @@
import loxi
import struct
-def unpack_array(deserializer, element_size, buf):
+def unpack_list(reader, deserializer):
"""
- Deserialize an array of fixed length elements.
- The deserializer function should take a buffer and return the new object.
- """
- if len(buf) % element_size != 0: raise loxi.ProtocolError("invalid array length")
- 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, extra_len=0):
- """
- 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, minus extra_len.
- The deserializer function should take a buffer and return the new object.
+ The deserializer function should take an OFReader and return the new object.
"""
entries = []
- offset = 0
- length_struct = struct.Struct(length_fmt)
- n = len(buf)
- while offset < n:
- if offset + length_struct.size > len(buf): raise loxi.ProtocolError("entry header overruns list length")
- length, = length_struct.unpack_from(buf, offset)
- length += extra_len
- 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
+ while not reader.is_empty():
+ entries.append(deserializer(reader))
return entries
+def unpack_list_lv16(reader, deserializer):
+ """
+ The deserializer function should take an OFReader and return the new object.
+ """
+ def wrapper(reader):
+ length, = reader.peek('!H')
+ return deserializer(reader.slice(length))
+ return unpack_list(reader, wrapper)
+
+def unpack_list_tlv16(reader, deserializer):
+ """
+ The deserializer function should take an OFReader and an integer type
+ and return the new object.
+ """
+ def wrapper(reader):
+ typ, length, = reader.peek('!HH')
+ return deserializer(reader.slice(length), typ)
+ return unpack_list(reader, wrapper)
+
class OFReader(object):
"""
Cursor over a read-only buffer