Passed
Pull Request — master (#425)
by Carlos Eduardo
02:30
created

GenericStruct._unpack_attribute()   A

Complexity

Conditions 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3.2621

Importance

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