Passed
Pull Request — master (#498)
by Carlos Eduardo
02:09
created

MetaBitMask.__new__()   C

Complexity

Conditions 10

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 10

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 6
cts 6
cp 1
c 0
b 0
f 0
rs 6
cc 10
crap 10

How to fix   Complexity   

Complexity

Complex classes like MetaBitMask.__new__() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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