Passed
Push — master ( d80247...ba551d )
by Humberto
03:02 queued 10s
created

pyof.foundation.base.GenericType.isenum()   A

Complexity

Conditions 1

Size

Total Lines 8
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 8
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nop 1
crap 1
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')
37
38
# Classes
39
40
41 1
class GenericType:
42
    """Foundation class for all custom attributes.
43
44
    Base class for :class:`~.UBInt8`, :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 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__(cls, name, bases, **kwargs):
275 1
        return OrderedDict()
276
277 1
    def __new__(cls, 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 = cls._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__(cls, 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(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
            if 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
        if isinstance(value, type(self)):
659 1
            return value.get_size()
660
        msg = "{} is not an instance of {}".format(value, type(self).__name__)
661
        raise PackException(msg)
662
663 1
    def pack(self, value=None):
664
        """Pack the struct in a binary representation.
665
666
        Iterate over the class attributes, according to the
667
        order of definition, and then convert each attribute to its byte
668
        representation using its own ``pack`` method.
669
670
        Returns:
671
            bytes: Binary representation of the struct object.
672
673
        Raises:
674
            :exc:`~.exceptions.ValidationError`: If validation fails.
675
676
        """
677 1
        if value is None:
678 1
            if not self.is_valid():
679
                error_msg = "Error on validation prior to pack() on class "
680
                error_msg += "{}.".format(type(self).__name__)
681
                raise ValidationError(error_msg)
682 1
            message = b''
683
            # pylint: disable=no-member
684 1
            for attr_info in self._get_named_attributes():
685 1
                name, instance_value, class_value = attr_info
686 1
                try:
687 1
                    message += class_value.pack(instance_value)
688 1
                except PackException as pack_exception:
689 1
                    cls = type(self).__name__
690 1
                    msg = f'{cls}.{name} - {pack_exception}'
691 1
                    raise PackException(msg)
692 1
            return message
693 1
        if isinstance(value, type(self)):
694 1
            return value.pack()
695
        msg = "{} is not an instance of {}".format(value, type(self).__name__)
696
        raise PackException(msg)
697
698 1
    def unpack(self, buff, offset=0):
699
        """Unpack a binary struct into this object's attributes.
700
701
        Update this object attributes based on the unpacked values of *buff*.
702
        It is an inplace method and it receives the binary data of the struct.
703
704
        Args:
705
            buff (bytes): Binary data package to be unpacked.
706
            offset (int): Where to begin unpacking.
707
        """
708 1
        begin = offset
709 1
        for name, value in self.get_class_attributes():
710 1
            size = self._unpack_attribute(name, value, buff, begin)
711 1
            begin += size
712
713 1
    def is_valid(self):
714
        """Check whether all struct attributes in are valid.
715
716
        This method will check whether all struct attributes have a proper
717
        value according to the OpenFlow specification. For instance, if you
718
        have a struct with an attribute of type
719
        :class:`~pyof.foundation.basic_types.UBInt8` and you assign a string
720
        value to it, this method will return False.
721
722
        Returns:
723
            bool: Whether the struct is valid.
724
725
        """
726 1
        return True
727
        # pylint: disable=unreachable
728
        return self._validate_attributes_type()
729
730
731 1
class GenericMessage(GenericStruct):
732
    """Base class that is the foundation for all OpenFlow messages.
733
734
    To add a method that will be used by all messages, write it here.
735
736
    .. note:: A Message on this library context is like a Struct but has a
737
              also a :attr:`header` attribute.
738
    """
739
740 1
    header = None
741
742 1
    def __init__(self, xid=None):
743
        """Initialize header's xid."""
744 1
        super().__init__()
745 1
        self.header.xid = randint(0, MAXID) if xid is None else xid
746
747 1
    def __repr__(self):
748
        """Show a full representation of the object."""
749
        return "%s(xid=%r)" % (self.__class__.__name__,
750
                               self.header.xid if self.header else None)
751
752 1
    def __init_subclass__(cls, **kwargs):
753 1
        if cls.header is None or cls.header.__class__.__name__ != 'Header':
754
            msg = "The header attribute must be implemented on the class "
755
            msg += cls.__name__ + "."
756
            raise NotImplementedError(msg)
757 1
        super().__init_subclass__(**kwargs)
758
759 1
    def _validate_message_length(self):
760
        return self.header.length == self.get_size()
761
762 1
    def is_valid(self):
763
        """Check whether a message is valid or not.
764
765
        This method will validate the Message content. During the validation
766
        process, we check whether the attributes' values are valid according to
767
        the OpenFlow specification. Call this method if you want to verify
768
        whether the message is ready to pack.
769
770
        Returns:
771
            bool: Whether the message is valid.
772
773
        """
774 1
        return True
775
        # pylint: disable=unreachable
776
        return super().is_valid() and self._validate_message_length()
777
778 1
    def pack(self, value=None):
779
        """Pack the message into a binary data.
780
781
        One of the basic operations on a Message is the pack operation. During
782
        the packing process, we convert all message attributes to binary
783
        format.
784
785
        Since that this is usually used before sending the message to a switch,
786
        here we also call :meth:`update_header_length`.
787
788
        .. seealso:: This method call its parent's :meth:`GenericStruct.pack`
789
            after :meth:`update_header_length`.
790
791
        Returns:
792
            bytes: A binary data thats represents the Message.
793
794
        Raises:
795
            Exception: If there are validation errors.
796
797
        """
798 1
        if value is None:
799 1
            self.update_header_length()
800 1
            return super().pack()
801
        if isinstance(value, type(self)):
802
            return value.pack()
803
        msg = "{} is not an instance of {}".format(value, type(self).__name__)
804
        raise PackException(msg)
805
806 1
    def unpack(self, buff, offset=0):
807
        """Unpack a binary message into this object's attributes.
808
809
        Unpack the binary value *buff* and update this object attributes based
810
        on the results. It is an inplace method and it receives the binary data
811
        of the message **without the header**.
812
813
        Args:
814
            buff (bytes): Binary data package to be unpacked, without the
815
                header.
816
            offset (int): Where to begin unpacking.
817
        """
818 1
        begin = offset
819 1
        for name, value in self.get_class_attributes():
820 1
            if type(value).__name__ != "Header":
821 1
                size = self._unpack_attribute(name, value, buff, begin)
822 1
                begin += size
823
824 1
    def update_header_length(self):
825
        """Update the header length attribute based on current message size.
826
827
        When sending an OpenFlow message we need to inform the message length
828
        on the header. This is mandatory.
829
        """
830 1
        self.header.length = self.get_size()
831
832
833 1
class MetaBitMask(type):
834
    """MetaClass to create a special BitMaskEnum type.
835
836
    You probably do not need to use this class. Inherit from
837
    :class:`GenericBitMask` instead.
838
839
    This metaclass converts the declared class attributes into elements of an
840
    enum. It also replaces the :meth:`__dir__` and :meth:`__getattr__` methods,
841
    so the resulting class will behave as an :class:`~Enum` class (you can
842
    access object.ELEMENT and recover either values or names).
843
    """
844
845 1
    def __new__(cls, name, bases, classdict):
846
        """Convert class attributes into enum elements."""
847 1
        _enum = OrderedDict([(key, value) for key, value in classdict.items()
848
                             if key[0] != '_' and not
849
                             hasattr(value, '__call__') and not
850
                             isinstance(value, property)])
851 1
        if _enum:
852 1
            classdict = {key: value for key, value in classdict.items()
853
                         if key[0] == '_' or hasattr(value, '__call__') or
854
                         isinstance(value, property)}
855 1
            classdict['_enum'] = _enum
856 1
        return type.__new__(cls, name, bases, classdict)
857
858 1
    def __getattr__(cls, name):
859 1
        return cls._enum[name]
860
861 1
    def __dir__(cls):
862
        res = dir(type(cls)) + list(cls.__dict__.keys())
863
        if cls is not GenericBitMask:
864
            res.extend(cls._enum)
865
        return res
866
867
868 1
class GenericBitMask(metaclass=MetaBitMask):
869
    """Base class for enums that use bitmask values."""
870
871 1
    def __init__(self, bitmask=None):
872
        """Create a GenericBitMask with the optional parameter below.
873
874
        Args:
875
            bitmask: Bitmask value.
876
        """
877 1
        self.bitmask = bitmask
878 1
        self._enum = {}
879
880 1
    def __str__(self):
881
        return "{}".format(self.bitmask)
882
883 1
    def __repr__(self):
884
        return "{}({})".format(type(self).__name__, self.bitmask)
885
886 1
    @property
887
    def names(self):
888
        """List of selected enum names.
889
890
        Returns:
891
            list: Enum names.
892
893
        """
894
        result = []
895
        for key, value in self.iteritems():
896
            if value & self.bitmask:
897
                result.append(key)
898
        return result
899
900 1
    def iteritems(self):
901
        """Create a generator for attributes' name-value pairs.
902
903
        Returns:
904
            generator: Attributes' (name, value) tuples.
905
906
        """
907
        for key, value in self._enum.items():
908
            yield (key, value)
909