Repository: kytos/python-openflow

Issue:
Review body attribute
Review `body` attribute of `...controller2switch.stats_request` and `...controller2switch.stats_reply` to evaluate if this attribute its type. It has been defined as a `ConstantTypeList`, but may be it could be a `UBInt8` (to act as a pointer) or a `BinaryData` generic_type field.

Comments:

Patch:
diff --git a/pyof/foundation/basic_types.py b/pyof/foundation/basic_types.py
index 297baa2..a3b5461 100644
--- a/pyof/foundation/basic_types.py
+++ b/pyof/foundation/basic_types.py
@@ -130,10 +130,11 @@ class DPID(GenericType):
         self._value = ':'.join(bytes)
 
     def pack(self, value=None):
-        buffer = b''
-        for value in self._value.split(":"):
-            buffer += struct.pack('!B', int(value, 16))
-        return buffer
+        if isinstance(value, type(self)):
+            return value.pack()
+        if value is None:
+            value = self._value
+        return struct.pack('!8B', *[int(v, 16) for v in value.split(':')])
 
 
 class Char(GenericType):
diff --git a/pyof/v0x01/common/utils.py b/pyof/v0x01/common/utils.py
index 81ded8f..274130c 100644
--- a/pyof/v0x01/common/utils.py
+++ b/pyof/v0x01/common/utils.py
@@ -11,7 +11,7 @@ from pyof.v0x01.asynchronous.flow_removed import FlowRemoved
 from pyof.v0x01.asynchronous.packet_in import PacketIn
 from pyof.v0x01.asynchronous.port_status import PortStatus
 # Importing controller2switch messages
-from pyof.v0x01.common.header import Type
+from pyof.v0x01.common.header import Header, Type
 from pyof.v0x01.controller2switch.barrier_reply import BarrierReply
 from pyof.v0x01.controller2switch.barrier_request import BarrierRequest
 from pyof.v0x01.controller2switch.features_reply import FeaturesReply
@@ -34,7 +34,8 @@ from pyof.v0x01.symmetric.echo_request import EchoRequest
 from pyof.v0x01.symmetric.hello import Hello
 from pyof.v0x01.symmetric.vendor_header import VendorHeader
 
-__all__ = ('new_message_from_header', 'new_message_from_message_type')
+__all__ = ('new_message_from_header', 'new_message_from_message_type',
+           'unpack_message')
 
 
 def new_message_from_message_type(message_type):
@@ -116,3 +117,14 @@ def new_message_from_header(header):
     message.header.length = header.length
 
     return message
+
+
+def unpack_message(buffer):
+    """Unpack the whole buffer, including header pack."""
+    hdr_size = Header().get_size()
+    hdr_buff, msg_buff = buffer[:hdr_size], buffer[hdr_size:]
+    header = Header()
+    header.unpack(hdr_buff)
+    message = new_message_from_header(header)
+    message.unpack(msg_buff)
+    return message
diff --git a/pyof/v0x01/controller2switch/features_reply.py b/pyof/v0x01/controller2switch/features_reply.py
index 8dff843..33f5541 100644
--- a/pyof/v0x01/controller2switch/features_reply.py
+++ b/pyof/v0x01/controller2switch/features_reply.py
@@ -62,7 +62,7 @@ class SwitchFeatures(GenericMessage):
 
         Args:
             xid (int): xid to be used on the message header.
-            datapath_id (int): UBInt64 datapath unique ID.
+            datapath_id (str or :class:`.DPID`): datapath unique ID.
                 The lower 48-bits are for MAC address, while
                 the upper 16-bits are implementer-defined.
             n_buffers (int): UBInt32 max packets buffered at once.
diff --git a/pyof/v0x01/controller2switch/stats_reply.py b/pyof/v0x01/controller2switch/stats_reply.py
index 1383aca..795ecce 100644
--- a/pyof/v0x01/controller2switch/stats_reply.py
+++ b/pyof/v0x01/controller2switch/stats_reply.py
@@ -1,14 +1,9 @@
 """Response the stat request packet from the controller."""
-
-# System imports
-
-# Third-party imports
-
 from pyof.foundation.base import GenericMessage
-from pyof.foundation.basic_types import BinaryData, UBInt16
-# Local imports
+from pyof.foundation.basic_types import BinaryData, FixedTypeList, UBInt16
 from pyof.v0x01.common.header import Header, Type
-from pyof.v0x01.controller2switch.common import FlowStats, StatsTypes
+from pyof.v0x01.controller2switch.common import (DescStats, PortStats,
+                                                 StatsTypes)
 
 __all__ = ('StatsReply',)
 
@@ -35,7 +30,7 @@ class StatsReply(GenericMessage):
         self.flags = flags
         self.body = body
 
-    def unpack(self, buff, offset=0):
+    def unpack(self, buff):
         """Unpack a binary message into this object's attributes.
 
         Unpack the binary value *buff* and update this object attributes based
@@ -43,29 +38,21 @@ class StatsReply(GenericMessage):
         of the message **without the header**.
 
         This class' unpack method is like the :meth:`.GenericMessage.unpack`
-        one, except for the ``actions`` attribute which has a length determined
-        by the ``actions_len`` attribute.
+        one, except for the ``body`` attribute which has its type determined
+        by the ``body_type`` attribute.
 
         Args:
             buff (bytes): Binary data package to be unpacked, without the
                 header.
-            offset (int): Where to begin unpacking.
         """
-        stats = []
-        super().unpack(buff, offset)
-        data = self.body.value
-        if self.body_type == StatsTypes.OFPST_FLOW:
-            ReplyClass = FlowStats
-        else:
-            # TODO: Implement other types
-            return
+        super().unpack(buff)
 
-        while len(data) > 0:
-            length = UBInt16()
-            length.unpack(data[:2])
-            item = ReplyClass()
-            item.unpack(data[0:length.value])
-            stats.append(item)
-            data = data[length.value:]
+        if self.body_type == StatsTypes.OFPST_PORT:
+            self._unpack_body(FixedTypeList(pyof_class=PortStats))
+        elif self.body_type == StatsTypes.OFPST_DESC:
+            self._unpack_body(DescStats())
 
-        self.body = stats
+    def _unpack_body(self, obj):
+        """Unpack `body` using `obj` and replace it by the result."""
+        obj.unpack(self.body.value)
+        self.body = obj
diff --git a/pyof/v0x01/controller2switch/stats_request.py b/pyof/v0x01/controller2switch/stats_request.py
index be1a5cf..838dc41 100644
--- a/pyof/v0x01/controller2switch/stats_request.py
+++ b/pyof/v0x01/controller2switch/stats_request.py
@@ -8,7 +8,7 @@ from pyof.foundation.base import GenericMessage
 from pyof.foundation.basic_types import BinaryData, UBInt16
 # Local imports
 from pyof.v0x01.common.header import Header, Type
-from pyof.v0x01.controller2switch.common import StatsTypes
+from pyof.v0x01.controller2switch.common import PortStatsRequest, StatsTypes
 
 __all__ = ('StatsRequest',)
 
@@ -34,3 +34,26 @@ class StatsRequest(GenericMessage):
         self.body_type = body_type
         self.flags = flags
         self.body = body
+
+    def pack(self):
+        """Pack according to :attr:`body_type`.
+
+        Make `body` a binary pack before packing this object. Then, restore
+        body.
+        """
+        if self.body_type == StatsTypes.OFPST_PORT:
+            backup = self.body
+            self.body = self.body.pack()
+            pack = super().pack()
+            self.body = backup
+            return pack
+        else:
+            return super().pack()
+
+    def unpack(self, buff):
+        """Unpack according to :attr:`body_type`."""
+        super().unpack(buff)
+        if self.body_type == StatsTypes.OFPST_PORT:
+            buff = self.body.value
+            self.body = PortStatsRequest()
+            self.body.unpack(buff)
diff --git a/setup.cfg b/setup.cfg
index b16a827..deec796 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -3,8 +3,9 @@ exclude = .eggs,ENV,build,docs/conf.py,venv
 
 [pylama]
 linters = pyflakes,isort,pep8,mccabe,pep257,pylint
-ignore = D105,D203,I0011
+ignore = D105,D203,D213,I0011
 #  D105: Missing docstring in magic method [pep257]
 #  D203: 1 blank line required before class docstring (found 0) [pep257]
 #        Conflicts with D211: No blank lines allowed before class docstring
+# D213: Should be ignored by default, but sometimes it is not
 # I0011: I0011 Locally disabling no-member (E1101) [pylint]


Test patch:
diff --git a/tests/v0x01/test_controller2switch/test_features_reply.py b/tests/v0x01/test_controller2switch/test_features_reply.py
index 2728ded..3f56325 100644
--- a/tests/v0x01/test_controller2switch/test_features_reply.py
+++ b/tests/v0x01/test_controller2switch/test_features_reply.py
@@ -1,5 +1,5 @@
 """Echo request message tests."""
-from pyof.foundation.basic_types import HWAddress
+from pyof.foundation.basic_types import HWAddress, DPID
 from pyof.v0x01.common.phy_port import PhyPort, PortConfig, PortState
 from pyof.v0x01.controller2switch.features_reply import FeaturesReply
 from tests.test_struct import TestStruct
@@ -19,8 +19,9 @@ class TestFeaturesReply(TestStruct):
 
 
 def _get_kwargs():
-    return {'xid': 2, 'datapath_id': 1, 'n_buffers': 256, 'n_tables': 254,
-            'capabilities': 0x000000c7, 'actions': 4095, 'ports': _get_ports()}
+    return {'xid': 2, 'datapath_id': DPID('00:00:00:00:00:00:00:01'),
+            'n_buffers': 256, 'n_tables': 254, 'capabilities': 0x000000c7,
+            'actions': 4095, 'ports': _get_ports()}
 
 
 def _get_ports():
diff --git a/tests/v0x01/test_controller2switch/test_stats_request.py b/tests/v0x01/test_controller2switch/test_stats_request.py
index 1891f66..087afa6 100644
--- a/tests/v0x01/test_controller2switch/test_stats_request.py
+++ b/tests/v0x01/test_controller2switch/test_stats_request.py
@@ -1,7 +1,10 @@
 """Test for StatsRequest message."""
 import unittest
 
-from pyof.v0x01.controller2switch import common, stats_request
+from pyof.v0x01.common.phy_port import Port
+from pyof.v0x01.common.utils import unpack_message
+from pyof.v0x01.controller2switch.common import PortStatsRequest
+from pyof.v0x01.controller2switch.stats_request import StatsRequest, StatsTypes
 
 
 class TestStatsRequest(unittest.TestCase):
@@ -9,9 +12,9 @@ class TestStatsRequest(unittest.TestCase):
 
     def setUp(self):
         """Basic test setup."""
-        self.message = stats_request.StatsRequest()
+        self.message = StatsRequest()
         self.message.header.xid = 1
-        self.message.type = common.StatsTypes.OFPST_FLOW
+        self.message.type = StatsTypes.OFPST_FLOW
         self.message.flags = 1
         self.message.body = []
 
@@ -19,6 +22,15 @@ class TestStatsRequest(unittest.TestCase):
         """[Controller2Switch/StatsRequest] - size 12."""
         self.assertEqual(self.message.get_size(), 12)
 
+    def test_pack_unpack_port_stats(self):
+        """Pack and unpack PortStatsRequest."""
+        body = PortStatsRequest(Port.OFPP_NONE)
+        req = StatsRequest(16909060, body_type=StatsTypes.OFPST_PORT,
+                           body=body)
+        pack = req.pack()
+        unpacked = unpack_message(pack)
+        self.assertEqual(req, unpacked)
+
     @unittest.skip('Not yet implemented')
     def test_pack(self):
         """[Controller2Switch/StatsRequest] - packing."""