Passed
Push — master ( ce9903...7d5ffc )
by Beraldo
52s
created

GenericStruct   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 221
Duplicated Lines 0 %

Test Coverage

Coverage 64.94%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 36
c 3
b 0
f 0
dl 0
loc 221
ccs 50
cts 77
cp 0.6494
rs 8.8

13 Methods

Rating   Name   Duplication   Size   Complexity  
B _validate_attributes_type() 0 11 6
A __eq__() 0 10 1
A is_valid() 0 14 1
A __init__() 0 4 2
A _is_pyof_attribute() 0 12 1
A _get_attributes() 0 15 2
B get_size() 0 25 4
B pack() 0 30 5
B _get_class_attributes() 0 26 3
A _attr_fits_into_class() 0 8 3
A unpack() 0 14 2
A _get_instance_attributes() 0 15 3
A _unpack_attribute() 0 14 3
1
"""Base and fundamental classes used all over the library.
2
3
Besides classes, several constants are defined here. We designed
4
python-openflow in a manner to make it easy to create new messages and OpenFlow
5
structs. You can realize that when you see a message class definition.
6
7
A **struct** here is a group of basic attributes and/or struct attributes (i.e.
8
:class:`~.common.header.Header`). A **message** here is like a struct, but all
9
messages have a header attribute (i.e.
10
:class:`~.asynchronous.packet_in.PacketIn`).
11
12
The main classes of this module are :class:`GenericStruct`,
13
:class:`GenericMessage`, :class:`GenericBitMask` and :class:`GenericType`.
14
These classes are used in all parts of this library.
15
"""
16
17
# System imports
18 1
import importlib
19 1
import re
20 1
import struct
21 1
from collections import OrderedDict
22 1
from copy import deepcopy
23 1
from enum import Enum
24
25
# Local source tree imports
26 1
from pyof.foundation.exceptions import (BadValueException, PackException,
27
                                        UnpackException, ValidationError)
28
29
# Third-party imports
30
31
32
# This will determine the order on sphinx documentation.
33 1
__all__ = ('GenericStruct', 'GenericMessage', 'GenericType', 'GenericBitMask',
34
           'MetaStruct', 'MetaBitMask')
35
36
# Classes
37
38
39 1
class GenericType:
40
    """Foundation class for all custom attributes.
41
42
    Base class for :class:`~.UBInt8`, :class:`~.Char`
43
    and others.
44
    """
45
46 1
    _fmt = None
47
48 1
    def __init__(self, value=None, enum_ref=None):
49
        """The constructor takes the optional parameters below.
50
51
        Args:
52
            value: The type's value.
53
            enum_ref (:class:`type`): If :attr:`value` is from an Enum, specify
54
                its type.
55
        """
56 1
        self._value = value
57 1
        self.enum_ref = enum_ref
58
59 1
    def __repr__(self):
60
        return "{}({})".format(type(self).__name__, self._value)
61
62 1
    def __str__(self):
63 1
        return '{}'.format(str(self._value))
64
65 1
    def __eq__(self, other):
66 1
        if isinstance(other, self.__class__):
67 1
            return self.pack() == other.pack()
68 1
        elif hasattr(other, 'value'):
69 1
            return self.value == other.value
70 1
        return self.value == other
71
72 1
    def __ne__(self, other):
73 1
        return self._value != other
74
75 1
    def __gt__(self, other):
76
        return self._value > other
77
78 1
    def __ge__(self, other):
79
        return self._value >= other
80
81 1
    def __lt__(self, other):
82
        return self._value <= other
83
84 1
    def __le__(self, other):
85
        return self._value <= other
86
87 1
    def __add__(self, other):
88 1
        return self.value + other
89
90 1
    def __radd__(self, other):
91 1
        return self.value + other
92
93 1
    def __sub__(self, other):
94 1
        return self.value - other
95
96 1
    def __rsub__(self, other):
97 1
        return self.value - other
98
99 1
    def __or__(self, other):
100 1
        return self.value | other
101
102 1
    def __ror__(self, other):
103 1
        return self.value | other
104
105 1
    def __and__(self, other):
106 1
        return self.value & other
107
108 1
    def __rand__(self, other):
109 1
        return self.value & other
110
111 1
    def __xor__(self, other):
112 1
        return self.value ^ other
113
114 1
    def __rxor__(self, other):
115 1
        return self.value ^ other
116
117 1
    @property
118
    def value(self):
119
        """Return this type's value.
120
121
        The value of an enum, bitmask, etc.
122
        """
123 1
        if self.isenum():
124 1
            if isinstance(self._value, self.enum_ref):
125 1
                return self._value.value
126 1
            return self._value
127 1
        elif self.is_bitmask():
128 1
            return self._value.bitmask
129
        else:
130 1
            return self._value
131
132 1 View Code Duplication
    def pack(self, value=None):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
133
        r"""Pack the value as a binary representation.
134
135
        Considering an example with UBInt8 class, that inherits from
136
        GenericType:
137
138
        >>> from pyof.foundation.basic_types import UBInt8
139
        >>> objectA = UBInt8(1)
140
        >>> objectB = 5
141
        >>> objectA.pack()
142
        b'\x01'
143
        >>> objectA.pack(objectB)
144
        b'\x05'
145
146
        Args:
147
            value: If the value is None, then we will pack the value of the
148
                current instance. Otherwise, if value is an instance of the
149
                same type as the current instance, then we call the pack of the
150
                value object. Otherwise, we will use the current instance pack
151
                method on the passed value.
152
153
        Returns:
154
            bytes: The binary representation.
155
156
        Raises:
157
            :exc:`~.exceptions.BadValueException`: If the value does not
158
                fit the binary format.
159
        """
160 1
        if isinstance(value, type(self)):
161 1
            return value.pack()
162
163 1
        if value is None:
164 1
            value = self.value
165 1
        elif 'value' in dir(value):
166
            # if it is enum or bitmask gets only the 'int' value
167 1
            value = value.value
168
169 1
        try:
170 1
            return struct.pack(self._fmt, value)
171 1
        except struct.error:
172 1
            msg = '{} could not pack {} = {}.'.format(type(self).__name__,
173
                                                      type(value).__name__,
174
                                                      value)
175 1
            raise PackException(msg)
176
177 1
    def unpack(self, buff, offset=0):
178
        """Unpack *buff* into this object.
179
180
        This method will convert a binary data into a readable value according
181
        to the attribute format.
182
183
        Args:
184
            buff (bytes): Binary buffer.
185
            offset (int): Where to begin unpacking.
186
187
        Raises:
188
            :exc:`~.exceptions.UnpackException`: If unpack fails.
189
        """
190 1
        try:
191 1
            self._value = struct.unpack_from(self._fmt, buff, offset)[0]
192 1
            if self.enum_ref:
193 1
                self._value = self.enum_ref(self._value)
194
        except (struct.error, TypeError, ValueError) as e:
195
            msg = '{}; fmt = {}, buff = {}, offset = {}.'.format(e, self._fmt,
196
                                                                 buff, offset)
197
            raise UnpackException(msg)
198
199 1
    def get_size(self, value=None):
0 ignored issues
show
Unused Code introduced by
The argument value seems to be unused.
Loading history...
200
        """Return the size in bytes of this type.
201
202
        Returns:
203
            int: Size in bytes.
204
        """
205 1
        return struct.calcsize(self._fmt)
206
207 1
    def is_valid(self):
208
        """Check whether the value fits the binary format.
209
210
        Assert that :func:`pack` succeeds.
211
212
        Returns:
213
            bool: Whether the value is valid for this type.
214
        """
215
        try:
216
            self.pack()
217
            return True
218
        except BadValueException:
219
            return False
220
221 1
    def isenum(self):
222
        """Test whether it is an :class:`~Enum`.
223
224
        Returns:
225
            bool: Whether it is an :class:`~Enum`.
226
        """
227 1
        return self.enum_ref and issubclass(self.enum_ref, Enum)
228
229 1
    def is_bitmask(self):
230
        """Test whether it is a :class:`GenericBitMask`.
231
232
        Returns:
233
            bool: Whether it is a :class:`GenericBitMask`.
234
        """
235 1
        return self._value and issubclass(type(self._value), GenericBitMask)
236
237
238 1
class MetaStruct(type):
239
    """MetaClass that dinamically handles openflow version of class attributes.
240
241
    See more about it at:
242
        https://github.com/kytos/python-openflow/wiki/Version-Inheritance
243
244
    You do not need to use this class. Inherit from :class:`GenericStruct`
245
    instead.
246
    """
247
248
    # pylint: disable=unused-argument
249 1
    @classmethod
250
    def __prepare__(mcs, name, bases, **kwargs):
251 1
        return OrderedDict()
252
253 1
    def __new__(cls, name, bases, classdict, **kwargs):
254
        """Inherit attributes from parent class and update their versions.
255
256
        Here is the moment that the new class is going to be created. During
257
        this process, two things may be done.
258
259
        Firstly, we will look if the type of any parent classes is this
260
        MetaStruct. We will inherit from the first parent class that fits this
261
        requirement. If any is found, then we will get all attributes from this
262
        class and place them as class attributes on the class being created.
263
264
        Secondly, for each class attribute being inherited, we will make sure
265
        that the pyof version of this attribute is the same as the version of
266
        the current class being created. If it is not, then we will find out
267
        which is the class and module of that attribute, look for a version
268
        that matches the version of the current class and replace that
269
        attribute with the correct version.
270
271
        See this link for more information on why this is being done:
272
            - https://github.com/kytos/python-openflow/wiki/Version-Inheritance
273
        """
274
        #: Retrieving class attributes management markers
275 1
        removed_attributes = classdict.pop('_removed_attributes', [])
276
        # renamed_attributes = classdict.pop('_renamed_attributes', [])
277
        # reordered_attributes = classdict.pop('_reordered_attributes', {})
278
279 1
        curr_module = classdict.get('__module__')
280 1
        curr_version = MetaStruct.get_pyof_version(curr_module)
281
282 1
        inherited_attributes = None
283
284
        #: looking for (kytos) class attributes defined on the bases
285
        #: classes so we can copy them into the current class being created
286
        #: so we can "inherit" them as class attributes
287 1
        for base in bases:
288
            #: Check if we are inheriting from one of our classes.
289 1
            if isinstance(base, MetaStruct):
290 1
                inherited_attributes = OrderedDict()
291 1
                for attr_name, obj in base._get_class_attributes():
292
                    #: Get an updated version of this attribute,
293
                    #: considering the version of the current class being
294
                    #: created.
295 1
                    attr = MetaStruct.get_pyof_obj_new_version(attr_name, obj,
296
                                                               curr_version
297
                                                               )
298
299 1
                    if attr_name == 'header':
300
                        #: Here we are going to set the message_type on the
301
                        #: header, according to the message_type of the
302
                        #: parent class.
303 1
                        old_enum = obj.message_type
304 1
                        new_header = attr[1]
305 1
                        new_enum = new_header.__class__.message_type.enum_ref
306
                        #: This 'if' will be removed on the future with an
307
                        #: improved version of __init_subclass__ method of the
308
                        #: GenericMessage class
309 1
                        if old_enum:
310 1
                            msg_type_name = old_enum.name
311 1
                            new_type = new_enum[msg_type_name]
312 1
                            new_header.message_type = new_type
313 1
                        attr = (attr[0], new_header)
314
315 1
                    inherited_attributes.update([attr])
316
                #: We are going to inherit just from the 'closest parent'
317 1
                break
318
319
        #: If we have inherited something, then first we will remove the
320
        #: attributes marked to be removed on the 'removed_attributes' and
321
        #: after that we will update the inherited 'classdict' with the
322
        #: attributes from the current classdict.
323 1
        if inherited_attributes is not None:
324
            #: removing attributes set to be removed
325 1
            for attr_name in removed_attributes:
326 1
                inherited_attributes.pop(attr_name, None)
327
328
            #: Updating the inherited attributes with those defined on the
329
            #: body of the class being created.
330 1
            inherited_attributes.update(classdict)
331 1
            classdict = inherited_attributes
332
333 1
        return super().__new__(cls, name, bases, classdict, **kwargs)
334
335 1
    @staticmethod
336
    def get_pyof_version(module_fullname):
337
        """Get the module pyof version based on the module fullname.
338
339
        Args:
340
            module_fullname (str): The fullname of the module
341
                (e.g.: pyof.v0x01.common.header)
342
343
        Returns:
344
            version (str): The module version, on the format 'v0x0?' if any. Or
345
            None (None): If there isn't a version on the fullname.
346
        """
347 1
        ver_module_re = re.compile(r'(pyof\.)(v0x\d+)(\..*)')
348 1
        matched = ver_module_re.match(module_fullname)
349 1
        if matched:
350 1
            version = matched.group(2)
351
            # module = matched.group(3)
352 1
            return version
353 1
        return None
354
355 1
    @staticmethod
356
    def replace_pyof_version(module_fullname, version):
357
        """Replace the OF Version of a module fullname.
358
359
        Get's a module name (eg. 'pyof.v0x01.common.header') and returns it on
360
        a new 'version' (eg. 'pyof.v0x02.common.header').
361
362
        Args:
363
            module_fullname (str): The fullname of the module
364
                (e.g.: pyof.v0x01.common.header)
365
            version (str): The version to be 'inserted' on the module fullname.
366
367
        Returns:
368
            None (None): if the requested version is the same as the one of the
369
                module_fullname or if the module_fullname is not a 'OF version'
370
                specific module.
371
            new_module_fullname (str): The new module fullname, with the
372
                replaced version, on the format "pyof.v0x01.common.header".
373
        """
374 1
        module_version = MetaStruct.get_pyof_version(module_fullname)
375 1
        if not module_version or module_version == version:
376 1
            return None
377
        else:
378
            return module_fullname.replace(module_version, version)
379
380 1
    @staticmethod
381
    def get_pyof_obj_new_version(name, obj, new_version):
382
        """Return a class atrribute on a different pyof version.
383
384
        This method receives the name of a class attribute, the class attribute
385
        itself (object) and an openflow version.
386
        The attribute will be evaluated and from it we will recover its class
387
        and the module where the class was defined.
388
        If the module is a "python-openflow version specific module" (starts
389
        with "pyof.v0"), then we will get it's version and if it is different
390
        from the 'new_version', then we will get the module on the
391
        'new_version', look for the 'obj' class on the new module and return
392
        an instance of the new version of the 'obj'.
393
394
        Example:
395
            >> from pyof.v0x01.common.header import Header
396
            >> name = 'header'
397
            >> obj = Header()
398
            >> obj
399
            <pyof.v0x01.common.header.Header at 0x...>
400
            >> new_version = 'v0x02'
401
            >> MetaStruct.get_pyof_new_version(name, obj, new_version)
402
            ('header', <pyof.v0x02.common.header.Header at 0x...)
403
404
        Args:
405
            name (str): the name of the class attribute being handled.
406
            obj (object): the class attribute itself
407
            new_version (string): the pyof version in which you want the object
408
                'obj'.
409
410
        Return:
411
            (name, obj): A tuple in which the first item is the name of the
412
                class attribute (the same that was passed), and the second item
413
                is a instance of the passed class attribute. If the class
414
                attribute is not a pyof versioned attribute, then the same
415
                passed object is returned without any changes. Also, if the obj
416
                is a pyof versioned attribute, but it is already on the right
417
                version (same as new_version), then the passed obj is return.
418
        """
419 1
        if new_version is None:
420 1
            return (name, obj)
421
422 1
        cls = obj.__class__
423 1
        cls_name = cls.__name__
424 1
        cls_mod = cls.__module__
425
426
        #: If the module name does not starts with pyof.v0 then it is not a
427
        #: 'pyof versioned' module (OpenFlow specification defined), so we do
428
        #: not have anything to do with it.
429 1
        new_mod = MetaStruct.replace_pyof_version(cls_mod, new_version)
430 1
        if new_mod is not None:
431
            # Loads the module
432
            new_mod = importlib.import_module(new_mod)
433
            #: Get the class from the loaded module
434
            new_cls = getattr(new_mod, cls_name)
435
            #: return the tuple with the attribute name and the instance
436
            return (name, new_cls())
437
438 1
        return (name, obj)
439
440
441 1
class GenericStruct(object, metaclass=MetaStruct):
442
    """Class inherited by all OpenFlow structs.
443
444
    If you need to insert a method that will be used by all structs, this is
445
    the place to code it.
446
447
    .. note:: A struct on this library's context is like a struct in C. It
448
              has a list of attributes and theses attributes can be structs,
449
              too.
450
    """
451
452 1
    def __init__(self):
453
        """Contructor takes no argument and stores attributes' deep copies."""
454 1
        for name, value in self._get_class_attributes():
455 1
            setattr(self, name, deepcopy(value))
456
457 1
    def __eq__(self, other):
458
        """Check whether two structures have the same structure and values.
459
460
        Compare the binary representation of structs to decide whether they
461
        are equal or not.
462
463
        Args:
464
            other (GenericStruct): The struct to be compared with.
465
        """
466 1
        return self.pack() == other.pack()
467
468 1
    @staticmethod
469
    def _attr_fits_into_class(attr, cls):
470
        if not isinstance(attr, cls):
471
            try:
472
                struct.pack(cls._fmt, attr)  # pylint: disable=protected-access
473
            except struct.error:
474
                return False
475
        return True
476
477 1
    @staticmethod
478
    def _is_pyof_attribute(obj):
479
        """Return True if the object is a kytos attribute.
480
481
        To be a kytos attribute the item must be an instance of either
482
        GenericType or GenericStruct.
483
484
        returns:
485
            True: if the obj is a kytos attribute
486
            False: if the obj is not a kytos attribute
487
        """
488 1
        return isinstance(obj, GenericType) or isinstance(obj, GenericStruct)
489
490 1
    def _validate_attributes_type(self):
491
        """Validate the type of each attribute."""
492
        for _attr, _class in self._get_attributes():
493
            if isinstance(_attr, _class):
494
                return True
495
            elif issubclass(_class, GenericType):
496
                if GenericStruct._attr_fits_into_class(_attr, _class):
497
                    return True
498
            elif not isinstance(_attr, _class):
499
                return False
500
        return True
501
502 1
    @classmethod
503
    def _get_class_attributes(cls):
504
        """Return a generator for class attributes' names and value.
505
506
        This method strict relies on the PEP 520 (Preserving Class Attribute
507
        Definition Order), implemented on Python 3.6. So, if this behaviour
508
        changes this whole lib can loose its functionality (since the
509
        attributes order are a strong requirement.) For the same reason, this
510
        lib will not work on python versions earlier than 3.6.
511
512
        .. code-block:: python3
513
514
            for _name, _value in self._get_class_attributes():
515
                print("attribute name: {}".format(_name))
516
                print("attribute type: {}".format(_value))
517
518
        returns:
519
            generator: tuples with attribute name and value.
520
        """
521
        #: see this method docstring for a important notice about the use of
522
        #: cls.__dict__
523 1
        for name, value in cls.__dict__.items():
524
            # gets only our (kytos) attributes. this ignores methods, dunder
525
            # methods and attributes, and common python type attributes.
526 1
            if GenericStruct._is_pyof_attribute(value):
527 1
                yield (name, value)
528
529 1
    def _get_instance_attributes(self):
530
        """Return a generator for instance attributes' name and value.
531
532
        .. code-block:: python3
533
534
            for _name, _value in self._get_instance_attributes():
535
                print("attribute name: {}".format(_name))
536
                print("attribute value: {}".format(_value))
537
538
        returns:
539
            generator: tuples with attribute name and value.
540
        """
541 1
        for name, value in self.__dict__.items():
542 1
            if name in map((lambda x: x[0]), self._get_class_attributes()):
543 1
                yield (name, value)
544
545 1
    def _get_attributes(self):
546
        """Return a generator for instance and class attribute.
547
548
        .. code-block:: python3
549
550
            for instance_attribute, class_attribute in self._get_attributes():
551
                print("Instance Attribute: {}".format(instance_attribute))
552
                print("Class Attribute: {}".format(class_attribute))
553
554
        Returns:
555
            generator: Tuples with instance attribute and class attribute
556
        """
557 1
        return map((lambda i, c: (i[1], c[1])),
558
                   self._get_instance_attributes(),
559
                   self._get_class_attributes())
560
561 1
    def _unpack_attribute(self, name, obj, buff, begin):
562 1
        attribute = deepcopy(obj)
563 1
        setattr(self, name, attribute)
564 1
        if len(buff) == 0:
565 1
            size = 0
566
        else:
567 1
            try:
568 1
                attribute.unpack(buff, begin)
569 1
                size = attribute.get_size()
570
            except UnpackException as e:
571
                child_cls = type(self).__name__
572
                msg = '{}.{}; {}'.format(child_cls, name, e)
573
                raise UnpackException(msg)
574 1
        return size
575
576 1
    def get_size(self, value=None):
577
        """Calculate the total struct size in bytes.
578
579
        For each struct attribute, sum the result of each one's ``get_size()``
580
        method.
581
582
        Args:
583
            value: In structs, the user can assign other value instead of a
584
                class' instance.
585
586
        Returns:
587
            int: Total number of bytes used by the struct.
588
589
        Raises:
590
            Exception: If the struct is not valid.
591
        """
592 1
        if value is None:
593 1
            return sum(cls_val.get_size(obj_val) for obj_val, cls_val in
594
                       self._get_attributes())
595 1
        elif isinstance(value, type(self)):
596 1
            return value.get_size()
597
        else:
598
            msg = "{} is not an instance of {}".format(value,
599
                                                       type(self).__name__)
600
            raise PackException(msg)
601
602 1
    def pack(self, value=None):
603
        """Pack the struct in a binary representation.
604
605
        Iterate over the class attributes, according to the
606
        order of definition, and then convert each attribute to its byte
607
        representation using its own ``pack`` method.
608
609
        Returns:
610
            bytes: Binary representation of the struct object.
611
612
        Raises:
613
            :exc:`~.exceptions.ValidationError`: If validation fails.
614
        """
615 1
        if value is None:
616 1
            if not self.is_valid():
617
                error_msg = "Error on validation prior to pack() on class "
618
                error_msg += "{}.".format(type(self).__name__)
619
                raise ValidationError(error_msg)
620
            else:
621 1
                message = b''
622
                # pylint: disable=no-member
623 1
                for instance_attr, class_attr in self._get_attributes():
624 1
                    message += class_attr.pack(instance_attr)
625 1
                return message
626 1
        elif isinstance(value, type(self)):
627 1
            return value.pack()
628
        else:
629
            msg = "{} is not an instance of {}".format(value,
630
                                                       type(self).__name__)
631
            raise PackException(msg)
632
633 1
    def unpack(self, buff, offset=0):
634
        """Unpack a binary struct into this object's attributes.
635
636
        Update this object attributes based on the unpacked values of *buff*.
637
        It is an inplace method and it receives the binary data of the struct.
638
639
        Args:
640
            buff (bytes): Binary data package to be unpacked.
641
            offset (int): Where to begin unpacking.
642
        """
643 1
        begin = offset
644 1
        for name, value in self._get_class_attributes():
645 1
            size = self._unpack_attribute(name, value, buff, begin)
646 1
            begin += size
647
648 1
    def is_valid(self):
649
        """Check whether all struct attributes in are valid.
650
651
        This method will check whether all struct attributes have a proper
652
        value according to the OpenFlow specification. For instance, if you
653
        have a struct with an attribute of type :class:`UBInt8()`
654
        and you assign a string value to it, this method will return False.
655
656
        Returns:
657
            bool: Whether the struct is valid.
658
        """
659 1
        return True
660
        # pylint: disable=unreachable
661
        return self._validate_attributes_type()
662
663
664 1
class GenericMessage(GenericStruct):
665
    """Base class that is the foundation for all OpenFlow messages.
666
667
    To add a method that will be used by all messages, write it here.
668
669
    .. note:: A Message on this library context is like a Struct but has a
670
              also a :attr:`header` attribute.
671
    """
672
673 1
    header = None
674
675 1
    def __init__(self, xid=None):
676
        """Initialize header's xid."""
677 1
        super().__init__()
678 1
        if xid is not None:
679 1
            self.header.xid = xid
680
681 1
    def __init_subclass__(cls, **kwargs):
682 1
        if cls.header is None or cls.header.__class__.__name__ != 'Header':
683
            msg = "The header attribute must be implemented on the class "
684
            msg += cls.__name__ + "."
685
            raise NotImplementedError(msg)
686 1
        super().__init_subclass__(**kwargs)
687
688 1
    def _validate_message_length(self):
689
        return self.header.length == self.get_size()
690
691 1
    def is_valid(self):
692
        """Check whether a message is valid or not.
693
694
        This method will validate the Message content. During the validation
695
        process, we check whether the attributes' values are valid according to
696
        the OpenFlow specification. Call this method if you want to verify
697
        whether the message is ready to pack.
698
699
        Returns:
700
            bool: Whether the message is valid.
701
        """
702 1
        return True
703
        # pylint: disable=unreachable
704
        return super().is_valid() and self._validate_message_length()
705
706 1
    def pack(self, value=None):
707
        """Pack the message into a binary data.
708
709
        One of the basic operations on a Message is the pack operation. During
710
        the packing process, we convert all message attributes to binary
711
        format.
712
713
        Since that this is usually used before sending the message to a switch,
714
        here we also call :meth:`update_header_length`.
715
716
        .. seealso:: This method call its parent's :meth:`GenericStruct.pack`
717
            after :meth:`update_header_length`.
718
719
        Returns:
720
            bytes: A binary data thats represents the Message.
721
722
        Raises:
723
            Exception: If there are validation errors.
724
        """
725 1
        if value is None:
726 1
            self.update_header_length()
727 1
            return super().pack()
728
        elif isinstance(value, type(self)):
729
            return value.pack()
730
        else:
731
            msg = "{} is not an instance of {}".format(value,
732
                                                       type(self).__name__)
733
            raise PackException(msg)
734
735 1
    def unpack(self, buff, offset=0):
736
        """Unpack a binary message into this object's attributes.
737
738
        Unpack the binary value *buff* and update this object attributes based
739
        on the results. It is an inplace method and it receives the binary data
740
        of the message **without the header**.
741
742
        Args:
743
            buff (bytes): Binary data package to be unpacked, without the
744
                header.
745
            offset (int): Where to begin unpacking.
746
        """
747 1
        begin = offset
748 1
        for name, value in self._get_class_attributes():
749 1
            if type(value).__name__ != "Header":
750 1
                size = self._unpack_attribute(name, value, buff, begin)
751 1
                begin += size
752
753 1
    def update_header_length(self):
754
        """Update the header length attribute based on current message size.
755
756
        When sending an OpenFlow message we need to inform the message length
757
        on the header. This is mandatory.
758
        """
759 1
        self.header.length = self.get_size()
760
761
762 1
class MetaBitMask(type):
763
    """MetaClass to create a special BitMaskEnum type.
764
765
    You probably do not need to use this class. Inherit from
766
    :class:`GenericBitMask` instead.
767
768
    This metaclass converts the declared class attributes into elements of an
769
    enum. It also replaces the :meth:`__dir__` and :meth:`__getattr__` methods,
770
    so the resulting class will behave as an :class:`~Enum` class (you can
771
    access object.ELEMENT and recover either values or names).
772
    """
773
774 1
    def __new__(mcs, name, bases, classdict):
775
        """Convert class attributes into enum elements."""
776 1
        _enum = OrderedDict([(key, value) for key, value in classdict.items()
777
                             if key[0] != '_' and not
778
                             hasattr(value, '__call__') and not
779
                             isinstance(value, property)])
780 1
        if _enum:
781 1
            classdict = {key: value for key, value in classdict.items()
782
                         if key[0] == '_' or hasattr(value, '__call__') or
783
                         isinstance(value, property)}
784 1
            classdict['_enum'] = _enum
785 1
        return type.__new__(mcs, name, bases, classdict)
786
787 1
    def __getattr__(cls, name):
788 1
        return cls._enum[name]
789
790 1
    def __dir__(cls):
791
        res = dir(type(cls)) + list(cls.__dict__.keys())
792
        if cls is not GenericBitMask:
793
            res.extend(cls._enum)
794
        return res
795
796
797 1
class GenericBitMask(object, metaclass=MetaBitMask):
798
    """Base class for enums that use bitmask values."""
799
800 1
    def __init__(self, bitmask=None):
801
        """The constructor has the optional parameter below.
802
803
        Args:
804
            bitmask: Bitmask value.
805
        """
806 1
        self.bitmask = bitmask
807 1
        self._enum = {}
808
809 1
    def __str__(self):
810
        return "{}".format(self.bitmask)
811
812 1
    def __repr__(self):
813
        return "{}({})".format(type(self).__name__, self.bitmask)
814
815 1
    @property
816
    def names(self):
817
        """List of selected enum names.
818
819
        Returns:
820
            list: Enum names.
821
        """
822
        result = []
823
        for key, value in self.iteritems():
824
            if value & self.bitmask:
825
                result.append(key)
826
        return result
827
828 1
    def iteritems(self):
829
        """Generator for attributes' name-value pairs.
830
831
        Returns:
832
            generator: Attributes' (name, value) tuples.
833
        """
834
        for key, value in self._enum.items():
835
            yield (key, value)
836