Repository: kytos/python-openflow

Issue:
OF 1.3 Compliance: Missing class ofp_meter_config
Class `ofp_meter_config` is missing.
Page 73 of 1.3 Spec.
```
struct ofp_meter_config {
	uint16_t length;
	uint16_t flags;
	uint32_t meter_id;
	struct ofp_meter_band_header bands[0]; 
};
OFP_ASSERT(sizeof(struct ofp_meter_config) == 8);
```

Comments:

Patch:
diff --git a/pyof/v0x04/controller2switch/common.py b/pyof/v0x04/controller2switch/common.py
index d4f740b..048d5f2 100644
--- a/pyof/v0x04/controller2switch/common.py
+++ b/pyof/v0x04/controller2switch/common.py
@@ -14,6 +14,9 @@ from pyof.v0x04.asynchronous.port_status import PortReason
 from pyof.v0x04.common.action import ActionHeader
 from pyof.v0x04.common.flow_match import Match
 from pyof.v0x04.common.header import Header
+from pyof.v0x04.controller2switch.meter_mod import (Meter, MeterFlags,
+                                                    MeterBandHeader,
+                                                    ListOfMeterBandHeader)
 
 # Third-party imports
 
@@ -23,7 +26,8 @@ __all__ = ('AggregateStatsReply', 'AggregateStatsRequest', 'Bucket',
            'GroupDescStats', 'GroupFeatures', 'GroupStats',
            'GroupStatsRequest', 'ListOfActions', 'MultipartTypes', 'PortStats',
            'PortStatsRequest', 'QueueStats', 'QueueStatsRequest', 'StatsTypes',
-           'TableStats')
+           'TableStats', 'MeterMultipartRequest', 'MeterConfig')
+
 
 # Enums
 
@@ -857,3 +861,89 @@ class SwitchConfig(GenericMessage):
         super().__init__(xid)
         self.flags = flags
         self.miss_send_len = miss_send_len
+
+
+class MeterMultipartRequest(GenericStruct):
+    """MeterMultipartRequest structure.
+
+    This class represents the structure for ofp_meter_multipart_request.
+    This structure is a body of OFPMP_METER and OFPMP_METER_CONFIG requests.
+    """
+
+    # Meter instance, or OFPM_ALL.
+    meter_id = UBInt32(enum_ref=Meter)
+
+    # Align to 64 bits.
+    pad = Pad(4)
+
+    def __init__(self, meter_id=Meter.OFPM_ALL):
+        """The Constructor of MeterMultipartRequest receives the paramters
+        below.
+
+        Args:
+            meter_id(Meter): Meter Indentify.The value Meter.OFPM_ALL is used
+                             to refer to all Meters on the switch.
+        """
+
+        super().__init__()
+        self.meter_id = meter_id
+
+
+class MeterConfig(GenericStruct):
+    """MeterConfig is a class to represents  ofp_meter_config structure.
+
+    Body of reply to OFPMP_METER_CONFIG request.
+    """
+    # Length of this entry.
+    length = UBInt16()
+    # All OFPMC_* that apply.
+    flags = UBInt16(enum_ref=MeterFlags)
+    # Meter instance.
+    meter_id = UBInt32(enum_ref=Meter)
+    # The bands length is inferred from the length field.
+    bands = ListOfMeterBandHeader()
+
+    def __init__(self, flags=MeterFlags.OFPMF_STATS, meter_id=Meter.OFPM_ALL,
+                 bands=[]):
+        """The Constructor of MeterConfig receives the parameters below.
+
+        Args:
+            flags(MeterFlags): Meter configuration flags.The default value is
+                               MeterFlags.OFPMF_STATS
+            meter_id(Meter):   Meter Indentify.The value Meter.OFPM_ALL is used
+                               to refer to all Meters on the switch.
+            bands(list):       List of MeterBandHeader instances.
+        """
+        super().__init__()
+        self.flags = flags
+        self.meter_id = meter_id
+        self.bands = bands
+        self.update_length()
+
+    def update_length(self):
+        self.length = self.get_size()
+
+
+    def pack(self, value=None):
+        """Pack method used to update the length of instance and  packing.
+
+        Args:
+            value: Structure to be packed.
+        """
+        self.update_length()
+        return super().pack(value)
+
+    def unpack(self, buff=None, offset=0):
+        """Unpack *buff* into this object.
+        This method will convert a binary data into a readable value according
+        to the attribute format.
+        Args:
+            buff (bytes): Binary buffer.
+            offset (int): Where to begin unpacking.
+        Raises:
+            :exc:`~.exceptions.UnpackException`: If unpack fails.
+        """
+        length = UBInt16()
+        length.unpack(buff,offset)
+
+        super().unpack(buff[:offset+length.value],offset)
diff --git a/pyof/v0x04/controller2switch/meter_mod.py b/pyof/v0x04/controller2switch/meter_mod.py
index 1d2786a..ea88335 100644
--- a/pyof/v0x04/controller2switch/meter_mod.py
+++ b/pyof/v0x04/controller2switch/meter_mod.py
@@ -60,6 +60,11 @@ class MeterBandType(Enum):
     #: Experimenter meter band.
     OFPMBT_EXPERIMENTER = 0xFFFF
 
+    def find_class(self):
+        """Method used to return a class related with this type."""
+        types = {1: MeterBandDrop, 2: MeterBandDscpRemark,
+                 3: MeterBandExperimenter}
+        return types[self.value]
 
 class MeterBandHeader(GenericStruct):
     """Common header for all meter bands."""
@@ -69,21 +74,45 @@ class MeterBandHeader(GenericStruct):
     rate = UBInt32()
     burst_size = UBInt32()
 
-    def __init__(self, band_type=None, length=None, rate=None,
-                 burst_size=None):
+    def __init__(self, band_type=None, rate=None, burst_size=None):
         """Instance attributes assignments.
 
         Args:
             band_type (MeterBandType): One of OFPMBT_*.
-            length (int): Length in bytes of this band.
             rate (int): Rate for this band.
             burst_size (int): Size of bursts.
         """
         super().__init__()
         self.band_type = band_type
-        self.length = length
         self.rate = rate
         self.burst_size = burst_size
+        self.update_length()
+
+    def update_length(self):
+        """Update the length attribute of current instance."""
+        self.length = self.get_size()
+
+    def unpack(self, buff=None, offset=0):
+        """Unpack *buff* into this object.
+
+        This method will convert a binary data into a readable value according
+        to the attribute format.
+
+        Args:
+            buff (bytes): Binary buffer.
+            offset (int): Where to begin unpacking.
+
+        Raises:
+            :exc:`~.exceptions.UnpackException`: If unpack fails.
+        """
+        band_type = UBInt16(enum_ref=MeterBandType)
+        band_type.unpack(buff, offset)
+        self.__class__ = MeterBandType(band_type.value).find_class()
+
+        length = UBInt16()
+        length.unpack(buff, offset=offset+2)
+
+        super().unpack(buff[:offset+length.value],offset)
 
 
 class MeterMod(GenericMessage):
@@ -114,80 +143,67 @@ class MeterMod(GenericMessage):
         self.bands = bands
 
 
-class MeterBandDrop(GenericStruct):
+class MeterBandDrop(MeterBandHeader):
     """OFPMBT_DROP band - drop packets."""
 
-    band_type = UBInt16(MeterBandType.OFPMBT_DROP, enum_ref=MeterBandType)
-    length = UBInt16()
-    rate = UBInt32()
-    burst_size = UBInt32()
     pad = Pad(4)
 
-    def __init__(self, length=None, rate=None, burst_size=None):
+    def __init__(self, rate=None, burst_size=None):
         """Instance attributes assignment.
 
         Args:
-            length (int): Length in bytes of this band.
             rate (int): Rate for dropping packets.
             burst_size (int): Size of bursts.
         """
-        super().__init__()
-        self.length = length
-        self.rate = rate
-        self.burst_size = burst_size
+        super().__init__(MeterBandType.OFPMBT_DROP, rate, burst_size)
 
 
-class MeterBandDscpRemark(GenericStruct):
+class MeterBandDscpRemark(MeterBandHeader):
     """OFPMBT_DSCP_REMARK band - Remark DSCP in the IP header."""
 
-    band_type = UBInt16(MeterBandType.OFPMBT_DSCP_REMARK,
-                        enum_ref=MeterBandType)
-    length = UBInt16()
-    rate = UBInt32()
-    burst_size = UBInt32()
     prec_level = UBInt8()
     pad = Pad(3)
 
-    def __init__(self, length=None, rate=None, burst_size=None,
-                 prec_level=None):
+    def __init__(self, rate=None, burst_size=None, prec_level=None):
         """Instance attributes assignment.
 
         Args:
-            length (int): Length in bytes of this band.
             rate (int): Rate for remarking packets.
             burst_size (int): Size of bursts.
             prec_level (int): Number of precendence level to substract.
         """
-        super().__init__()
-        self.length = length
-        self.rate = rate
-        self.burst_size = burst_size
+        super().__init__(MeterBandType.OFPMBT_DSCP_REMARK, rate, burst_size)
         self.prec_level = prec_level
 
 
-class MeterBandExperimenter(GenericStruct):
+class MeterBandExperimenter(MeterBandHeader):
     """OFPMBT_EXPERIMENTER band - Write actions in action set."""
 
-    band_type = UBInt16(MeterBandType.OFPMBT_EXPERIMENTER,
-                        enum_ref=MeterBandType)
-    length = UBInt16()
-    rate = UBInt32()
-    burst_size = UBInt32()
     experimenter = UBInt32()
 
-    def __init__(self, length=None, rate=None, burst_size=None,
-                 experimenter=None):
+    def __init__(self, rate=None, burst_size=None, experimenter=None):
         """Instance attributes assignment.
 
         Args:
-            length (int): Length in bytes of this band.
             rate (int): Rate for remarking packets.
             burst_size (int): Size of bursts.
             experimenter (int): Experimenter ID which takes the same form as in
                 :class:`.ExperimenterHeader`.
         """
-        super().__init__()
-        self.length = length
-        self.rate = rate
-        self.burst_size = burst_size
+        super().__init__(MeterBandType.OFPMBT_EXPERIMENTER, rate, burst_size)
         self.experimenter = experimenter
+
+
+class ListOfMeterBandHeader(FixedTypeList):
+    """List of MeterBandHeader.
+
+    Represented by instances of MeterBandHeader.
+    """
+
+    def __init__(self, items=[]):
+        """The constructor just assings parameters to object attributes.
+
+        Args:
+        items (MeterBandHeader): Instance or a list of instances.
+        """
+        super().__init__(pyof_class=MeterBandHeader,items=items)


Test patch:
diff --git a/tests/v0x04/test_controller2switch/test_multipart_reply.py b/tests/v0x04/test_controller2switch/test_multipart_reply.py
index 814fccd..3b28cf1 100644
--- a/tests/v0x04/test_controller2switch/test_multipart_reply.py
+++ b/tests/v0x04/test_controller2switch/test_multipart_reply.py
@@ -1,16 +1,36 @@
 """MultipartReply message test."""
-from pyof.v0x04.controller2switch.multipart_reply import MultipartReply
-from tests.test_struct import TestStruct
 
+from pyof.v0x04.controller2switch.common import (MeterConfig, MultipartTypes)
+from pyof.v0x04.controller2switch.meter_mod import (MeterFlags, Meter,
+                                                    MeterBandDrop,
+                                                    MeterBandDscpRemark,
+                                                    ListOfMeterBandHeader)
+from pyof.v0x04.controller2switch.multipart_reply import (MultipartReply,
+                                                          MultipartReplyFlags)
+from tests.v0x04.test_struct import TestStruct
 
-class TestMultipartReply(TestStruct):
-    """Test the MultipartReply message."""
+class TestTableFeatures(TestStruct):
+    """"""
 
     @classmethod
     def setUpClass(cls):
         """Configure raw file and its object in parent class (TestDump)."""
         super().setUpClass()
-        super().set_raw_dump_file('v0x04', 'ofpt_multipart_reply')
-        super().set_raw_dump_object(MultipartReply, xid=3, multipart_type=0,
-                                    flags=1, body=0)
+        super().set_message(MultipartReply, xid=16,
+                            multipart_type=MultipartTypes.OFPMP_METER_CONFIG,
+                            flags=MultipartReplyFlags.OFPMPF_REPLY_MORE,
+                            body=cls.meter_config_instance())
         super().set_minimum_size(16)
+
+    @classmethod
+    def meter_config_instance(cls):
+        """Method used to create a MeterConfig instance."""
+        return MeterConfig(bands=cls.list_of_meters())
+
+    @staticmethod
+    def list_of_meters():
+        """Method used to instantiate a ListOfMeterBandHeader with some instances."""
+        meters = [MeterBandDrop(rate=6, burst_size=3),
+                  MeterBandDscpRemark(rate=1,burst_size=4,prec_level=2),
+                  MeterBandDrop(rate=9, burst_size=1)]
+        return ListOfMeterBandHeader(items=[meters])