Passed
Push — master ( ba551d...808eea )
by Humberto
03:04 queued 10s
created

pyof.foundation.base.GenericBitMask.names()   A

Complexity

Conditions 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 8.2077

Importance

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