Passed
Push — master ( 207ac2...6a3916 )
by Carlos Eduardo
02:57
created

GenericStruct.is_valid()   A

Complexity

Conditions 1

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1.037

Importance

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