Test Failed
Pull Request — master (#392)
by
unknown
01:22
created

GenericMessage._validate_message_length()   A

Complexity

Conditions 1

Size

Total Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

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