pyof.foundation.base.MetaStruct.__prepare__()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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