Passed
Push — master ( 73eb39...259507 )
by Humberto
03:24
created

GenericStruct._get_instance_attributes()   A

Complexity

Conditions 4

Size

Total Lines 16
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 4

Importance

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