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

GenericUBIntType   A

Complexity

Total Complexity 5

Size/Duplication

Total Lines 32
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 5
c 0
b 0
f 0
dl 0
loc 32
rs 10
ccs 8
cts 8
cp 1

3 Methods

Rating   Name   Duplication   Size   Complexity  
A _get_size() 0 2 1
A _pack() 0 2 1
A unpack() 0 22 3
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
                # print('unpacking: ', name, attribute, buff[begin:])
639
                attribute.unpack(buff, begin)
640
                # print('value: ', attribute)
641
                size = attribute.get_size()
642
            except UnpackException as e:
643 1
                child_cls = type(self).__name__
644 1
                msg = '{}.{}; {}'.format(child_cls, name, e)
645 1
                raise UnpackException(msg)
646 1
        return size
647
648 1
    def _work_or_pass(self, value, work_func):
649
        if value is None:
650
            return getattr(self, work_func)()
651
        elif isinstance(value, type(self)):
652
            return getattr(value, work_func)()
653
        else:
654
            try:
655
                new_item = type(self)(value)
656
            except:  # noqa - there is no generic Initialization Exception...
657
                msg = "{} is not an instance of {}".format(value,
658
                                                           type(self).__name__)
659 1
                raise PackException(msg)
660
            return getattr(new_item, work_func)()
661
662
    def _get_size(self):
663
        return sum(cls_val.get_size(obj_val)
664 1
                   for obj_val, cls_val in self._get_attributes())
665
666
    def get_size(self, value=None):
667
        """Calculate the total struct size in bytes.
668
669
        For each struct attribute, sum the result of each one's ``get_size()``
670
        method.
671
672
        Args:
673 1
            value: In structs, the user can assign other value instead of a
674
                class' instance.
675 1
676
        Returns:
677 1
            int: Total number of bytes used by the struct.
678 1
679 1
        Raises:
680
            Exception: If the struct is not valid.
681 1
        """
682 1
        return self._work_or_pass(value, '_get_size')
683
684
    def pack(self, value=None):
685
        """Pack the struct in a binary representation.
686 1
687
        Iterate over the class attributes, according to the
688 1
        order of definition, and then convert each attribute to its byte
689
        representation using its own ``pack`` method.
690
691 1
        Returns:
692
            bytes: Binary representation of the struct object.
693
694
        Raises:
695
            :exc:`~.exceptions.ValidationError`: If validation fails.
696
        """
697
        return self._work_or_pass(value, '_pack')
698
699
    def _pack(self):
700
        if not self.is_valid():
701
            error_msg = "Error on validation prior to pack() on class "
702 1
            error_msg += "{}.".format(type(self).__name__)
703
            raise ValidationError(error_msg)
704
        else:
705
            message = b''
706 1
            # pylint: disable=no-member
707
            for instance_attr, class_attr in self._get_attributes():
708
                message += class_attr.pack(instance_attr)
709
            return message
710
711
    def unpack(self, buff, offset=0):
712
        """Unpack a binary struct into this object's attributes.
713
714
        Update this object attributes based on the unpacked values of *buff*.
715
        It is an inplace method and it receives the binary data of the struct.
716
717
        Args:
718
            buff (bytes): Binary data package to be unpacked.
719
            offset (int): Where to begin unpacking.
720
        """
721
        begin = offset
722
        for name, value in self.get_class_attributes():
723
            size = self._unpack_attribute(name, value, buff, begin)
724
            # print('size', size)
725 1
            begin += size
726 1
727 1
    def is_valid(self):
728
        """Check whether all struct attributes in are valid.
729
730
        This method will check whether all struct attributes have a proper
731
        value according to the OpenFlow specification. For instance, if you
732
        have a struct with an attribute of type
733
        :class:`~pyof.foundation.basic_types.UBInt8` and you assign a string
734
        value to it, this method will return False.
735 1
736
        Returns:
737
            bool: Whether the struct is valid.
738
        """
739
        return True
740
        # pylint: disable=unreachable
741
        return self._validate_attributes_type()
742
743
744
class GenericMessage(GenericStruct):
745
    """Base class that is the foundation for all OpenFlow messages.
746
747 1
    To add a method that will be used by all messages, write it here.
748 1
749 1
    .. note:: A Message on this library context is like a Struct but has a
750 1
              also a :attr:`header` attribute.
751 1
    """
752
753 1
    header = None
754
755
    def __init__(self, xid=None):
756
        """Initialize header's xid."""
757
        super().__init__()
758
        if xid is not None:
759 1
            self.header.xid = xid
760
761
    def __init_subclass__(cls, **kwargs):
762 1
        if cls.header is None or cls.header.__class__.__name__ != 'Header':
763
            msg = "The header attribute must be implemented on the class "
764
            msg += cls.__name__ + "."
765
            raise NotImplementedError(msg)
766
        super().__init_subclass__(**kwargs)
767
768
    def _validate_message_length(self):
769
        return self.header.length == self.get_size()
770
771
    def is_valid(self):
772
        """Check whether a message is valid or not.
773
774 1
        This method will validate the Message content. During the validation
775
        process, we check whether the attributes' values are valid according to
776 1
        the OpenFlow specification. Call this method if you want to verify
777
        whether the message is ready to pack.
778
779
        Returns:
780 1
            bool: Whether the message is valid.
781 1
        """
782
        return True
783
        # pylint: disable=unreachable
784 1
        return super().is_valid() and self._validate_message_length()
785 1
786
    def _pack(self):
787 1
        self.update_header_length()
788 1
        return super()._pack()
789
790 1
    def unpack(self, buff, offset=0):
791
        """Unpack a binary message into this object's attributes.
792
793
        Unpack the binary value *buff* and update this object attributes based
794
        on the results. It is an inplace method and it receives the binary data
795
        of the message **without the header**.
796
797 1
        Args:
798
            buff (bytes): Binary data package to be unpacked, without the
799
                header.
800 1
            offset (int): Where to begin unpacking.
801
        """
802
        begin = offset
803
        for name, value in self.get_class_attributes():
804
            if type(value).__name__ != "Header":
805
                size = self._unpack_attribute(name, value, buff, begin)
806 1
                begin += size
807 1
808
    def update_header_length(self):
809 1
        """Update the header length attribute based on current message size.
810
811
        When sending an OpenFlow message we need to inform the message length
812 1
        on the header. This is mandatory.
813
        """
814
        self.header.length = self.get_size()
815 1
816
817
class MetaBitMask(type):
818
    """MetaClass to create a special BitMaskEnum type.
819
820
    You probably do not need to use this class. Inherit from
821
    :class:`GenericBitMask` instead.
822
823
    This metaclass converts the declared class attributes into elements of an
824
    enum. It also replaces the :meth:`__dir__` and :meth:`__getattr__` methods,
825
    so the resulting class will behave as an :class:`~Enum` class (you can
826
    access object.ELEMENT and recover either values or names).
827
    """
828 1
829
    def __new__(mcs, name, bases, classdict):
830
        """Convert class attributes into enum elements."""
831
        _enum = OrderedDict([(key, value) for key, value in classdict.items()
832
                             if key[0] != '_' and not
833
                             hasattr(value, '__call__') and not
834
                             isinstance(value, property)])
835
        if _enum:
836
            classdict = {key: value for key, value in classdict.items()
837
                         if key[0] == '_' or hasattr(value, '__call__') or
838
                         isinstance(value, property)}
839
            classdict['_enum'] = _enum
840
        return type.__new__(mcs, name, bases, classdict)
841
842
    def __getattr__(cls, name):
843
        return cls._enum[name]
844
845
    def __dir__(cls):
846
        res = dir(type(cls)) + list(cls.__dict__.keys())
847
        if cls is not GenericBitMask:
848
            res.extend(cls._enum)
849
        return res
850
851
852
class GenericBitMask(object, metaclass=MetaBitMask):
853
    """Base class for enums that use bitmask values."""
854
855
    def __init__(self, bitmask=None):
856
        """The constructor has the optional parameter below.
857
858
        Args:
859
            bitmask: Bitmask value.
860
        """
861
        self.bitmask = bitmask
862
        self._enum = {}
863
864
    def __str__(self):
865
        return "{}".format(self.bitmask)
866
867
    def __repr__(self):
868
        return "{}({})".format(type(self).__name__, self.bitmask)
869
870
    @property
871
    def names(self):
872
        """List of selected enum names.
873
874
        Returns:
875
            list: Enum names.
876
        """
877
        result = []
878
        for key, value in self.iteritems():
879
            if value & self.bitmask:
880
                result.append(key)
881
        return result
882
883
    def iteritems(self):
884
        """Generator for attributes' name-value pairs.
885
886
        Returns:
887
            generator: Attributes' (name, value) tuples.
888
        """
889
        for key, value in self._enum.items():
890
            yield (key, value)
891