Passed
Push — master ( b085fb...082312 )
by Humberto
03:44
created

pyof.foundation.base.GenericStruct.__init__()   A

Complexity

Conditions 2

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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