Test Failed
Pull Request — master (#392)
by
unknown
02:28
created

GenericStruct.__init__()   A

Complexity

Conditions 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 2

Importance

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