Merge pull request #31 from rlane/parse-of13

pyloxi: fix message parsing for OF 1.1+
diff --git a/py_gen/templates/message.py b/py_gen/templates/message.py
index 8bf11f1..6754b94 100644
--- a/py_gen/templates/message.py
+++ b/py_gen/templates/message.py
@@ -113,8 +113,8 @@
 
 def parse_message(buf):
     msg_ver, msg_type, msg_len, msg_xid = parse_header(buf)
-    if msg_ver != const.OFP_VERSION and msg_type != ofp.OFPT_HELLO:
-        raise loxi.ProtocolError("wrong OpenFlow version")
+    if msg_ver != const.OFP_VERSION and msg_type != const.OFPT_HELLO:
+        raise loxi.ProtocolError("wrong OpenFlow version (expected %d, got %d)" % (const.OFP_VERSION, msg_ver))
     if len(buf) != msg_len:
         raise loxi.ProtocolError("incorrect message size")
     if msg_type in parsers:
@@ -122,11 +122,16 @@
     else:
         raise loxi.ProtocolError("unexpected message type")
 
-:: # TODO fix for OF 1.1+
 def parse_flow_mod(buf):
-    if len(buf) < 56 + 2:
+:: if version == 1:
+:: offset = 57
+:: elif version >= 2:
+:: offset = 25
+:: #endif
+    if len(buf) < ${offset} + 1:
         raise loxi.ProtocolError("message too short")
-    cmd, = struct.unpack_from("!H", buf, 56)
+    # Technically uint16_t for OF 1.0
+    cmd, = struct.unpack_from("!B", buf, ${offset})
     if cmd in flow_mod_parsers:
         return flow_mod_parsers[cmd](buf)
     else:
@@ -256,7 +261,39 @@
 :: #endif
 }
 :: else:
-# TODO OF 1.3 multipart messages
+multipart_reply_parsers = {
+    const.OFPMP_DESC : desc_stats_reply.unpack,
+    const.OFPMP_FLOW : flow_stats_reply.unpack,
+    const.OFPMP_AGGREGATE : aggregate_stats_reply.unpack,
+    const.OFPMP_TABLE : table_stats_reply.unpack,
+    const.OFPMP_PORT_STATS : port_stats_reply.unpack,
+    const.OFPMP_QUEUE : queue_stats_reply.unpack,
+    const.OFPMP_GROUP : group_stats_reply.unpack,
+    const.OFPMP_GROUP_DESC : group_desc_stats_reply.unpack,
+    const.OFPMP_GROUP_FEATURES : group_features_stats_reply.unpack,
+    const.OFPMP_METER : meter_stats_reply.unpack,
+    const.OFPMP_METER_CONFIG : meter_config_stats_reply.unpack,
+    const.OFPMP_METER_FEATURES : meter_features_stats_reply.unpack,
+    const.OFPMP_TABLE_FEATURES : table_features_stats_reply.unpack,
+    const.OFPMP_PORT_DESC : port_desc_stats_reply.unpack,
+}
+
+multipart_request_parsers = {
+    const.OFPMP_DESC : desc_stats_request.unpack,
+    const.OFPMP_FLOW : flow_stats_request.unpack,
+    const.OFPMP_AGGREGATE : aggregate_stats_request.unpack,
+    const.OFPMP_TABLE : table_stats_request.unpack,
+    const.OFPMP_PORT_STATS : port_stats_request.unpack,
+    const.OFPMP_QUEUE : queue_stats_request.unpack,
+    const.OFPMP_GROUP : group_stats_request.unpack,
+    const.OFPMP_GROUP_DESC : group_desc_stats_request.unpack,
+    const.OFPMP_GROUP_FEATURES : group_features_stats_request.unpack,
+    const.OFPMP_METER : meter_stats_request.unpack,
+    const.OFPMP_METER_CONFIG : meter_config_stats_request.unpack,
+    const.OFPMP_METER_FEATURES : meter_features_stats_request.unpack,
+    const.OFPMP_TABLE_FEATURES : table_features_stats_request.unpack,
+    const.OFPMP_PORT_DESC : port_desc_stats_request.unpack,
+}
 :: #endif
 
 :: experimenter_ofclasses = [x for x in ofclasses if x.type_members[1].value == 4]
diff --git a/py_gen/tests/of10.py b/py_gen/tests/of10.py
index e593e0f..be93239 100644
--- a/py_gen/tests/of10.py
+++ b/py_gen/tests/of10.py
@@ -245,6 +245,21 @@
             else:
                 fn()
 
+    def test_parse_message(self):
+        expected_failures = []
+        for klass in self.klasses:
+            if not issubclass(klass, ofp.message.Message):
+                continue
+            def fn():
+                obj = klass(xid=42)
+                buf = obj.pack()
+                obj2 = ofp.message.parse_message(buf)
+                self.assertEquals(obj, obj2)
+            if klass in expected_failures:
+                self.assertRaises(Exception, fn)
+            else:
+                fn()
+
     def test_show(self):
         expected_failures = []
         for klass in self.klasses:
diff --git a/py_gen/tests/of11.py b/py_gen/tests/of11.py
index dd76b0d..07a5437 100644
--- a/py_gen/tests/of11.py
+++ b/py_gen/tests/of11.py
@@ -82,6 +82,21 @@
             else:
                 fn()
 
+    def test_parse_message(self):
+        expected_failures = []
+        for klass in self.klasses:
+            if not issubclass(klass, ofp.message.Message):
+                continue
+            def fn():
+                obj = klass(xid=42)
+                buf = obj.pack()
+                obj2 = ofp.message.parse_message(buf)
+                self.assertEquals(obj, obj2)
+            if klass in expected_failures:
+                self.assertRaises(Exception, fn)
+            else:
+                fn()
+
     def test_show(self):
         expected_failures = []
         for klass in self.klasses:
diff --git a/py_gen/tests/of12.py b/py_gen/tests/of12.py
index 6926382..c6c94ab 100644
--- a/py_gen/tests/of12.py
+++ b/py_gen/tests/of12.py
@@ -91,6 +91,21 @@
             else:
                 fn()
 
+    def test_parse_message(self):
+        expected_failures = []
+        for klass in self.klasses:
+            if not issubclass(klass, ofp.message.Message):
+                continue
+            def fn():
+                obj = klass(xid=42)
+                buf = obj.pack()
+                obj2 = ofp.message.parse_message(buf)
+                self.assertEquals(obj, obj2)
+            if klass in expected_failures:
+                self.assertRaises(Exception, fn)
+            else:
+                fn()
+
     def test_show(self):
         expected_failures = []
         for klass in self.klasses:
diff --git a/py_gen/tests/of13.py b/py_gen/tests/of13.py
index 9e6be97..349881f 100644
--- a/py_gen/tests/of13.py
+++ b/py_gen/tests/of13.py
@@ -113,6 +113,24 @@
             else:
                 fn()
 
+    def test_parse_message(self):
+        expected_failures = [
+            ofp.message.table_features_stats_reply,
+            ofp.message.table_features_stats_request,
+        ]
+        for klass in self.klasses:
+            if not issubclass(klass, ofp.message.Message):
+                continue
+            def fn():
+                obj = klass(xid=42)
+                buf = obj.pack()
+                obj2 = ofp.message.parse_message(buf)
+                self.assertEquals(obj, obj2)
+            if klass in expected_failures:
+                self.assertRaises(Exception, fn)
+            else:
+                fn()
+
     def test_show(self):
         expected_failures = []
         for klass in self.klasses: