Test Failed
Pull Request — master (#461)
by Diego Rabatone
01:50
created

MetaStruct.__new__()   C

Complexity

Conditions 7

Size

Total Lines 67

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 7.0061

Importance

Changes 3
Bugs 0 Features 0
Metric Value
dl 0
loc 67
ccs 19
cts 20
cp 0.95
c 3
b 0
f 0
rs 5.8743
cc 7
crap 7.0061

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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