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

GenericUBIntType.unpack()   A

Complexity

Conditions 3

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 22
ccs 4
cts 4
cp 1
rs 9.2
cc 3
crap 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
        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, value=None):
0 ignored issues
show
Unused Code introduced by
The argument value seems to be unused.
Loading history...
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: The module version, on the format 'v0x0?' if any. Or None
401
                  if there isn't a version on the fullname.
402
        """
403
        ver_module_re = re.compile(r'(pyof\.)(v0x\d+)(\..*)')
404
        matched = ver_module_re.match(module_fullname)
405
        if matched:
406
            version = matched.group(2)
407
            # module = matched.group(3)
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
        """Return a class atrribute 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
            >> from pyof.v0x01.common.header import Header
453
            >> name = 'header'
454 1
            >> obj = Header()
455 1
            >> obj
456
            <pyof.v0x01.common.header.Header at 0x...>
457 1
            >> new_version = 'v0x02'
458
            >> MetaStruct.get_pyof_new_version(name, obj, new_version)
459
            ('header', <pyof.v0x02.common.header.Header at 0x...)
460
461
        Args:
462
            name (str): the name of the class attribute being handled.
463
            obj (object): the class attribute itself
464
            new_version (string): the pyof version in which you want the object
465
                'obj'.
466 1
467
        Return:
468 1
            (str, obj): A tuple in which the first item is the name of the
469
                class attribute (the same that was passed), and the second item
470
                is a instance of the passed class attribute. If the class
471
                attribute is not a pyof versioned attribute, then the same
472
                passed object is returned without any changes. Also, if the obj
473
                is a pyof versioned attribute, but it is already on the right
474
                version (same as new_version), then the passed obj is return.
475
        """
476
        if new_version is None:
477 1
            return (name, obj)
478
479
        cls = obj.__class__
480
        cls_name = cls.__name__
481
        cls_mod = cls.__module__
482
483
        #: If the module name does not starts with pyof.v0 then it is not a
484
        #: 'pyof versioned' module (OpenFlow specification defined), so we do
485
        #: not have anything to do with it.
486
        new_mod = MetaStruct.replace_pyof_version(cls_mod, new_version)
487
        if new_mod is not None:
488 1
            # Loads the module
489
            new_mod = importlib.import_module(new_mod)
490 1
            #: Get the class from the loaded module
491
            new_cls = getattr(new_mod, cls_name)
492
            #: return the tuple with the attribute name and the instance
493
            return (name, new_cls())
494
495
        return (name, obj)
496
497
498
class GenericStruct(object, metaclass=MetaStruct):
499
    """Class inherited by all OpenFlow structs.
500
501
    If you need to insert a method that will be used by all structs, this is
502 1
    the place to code it.
503
504
    .. note:: A struct on this library's context is like a struct in C. It
505
              has a list of attributes and theses attributes can be structs,
506
              too.
507
    """
508
509
    def __init__(self):
510
        """Contructor takes no argument and stores attributes' deep copies."""
511
        for name, value in self.get_class_attributes():
512
            setattr(self, name, deepcopy(value))
513
514
    def __eq__(self, other):
515
        """Check whether two structures have the same structure and values.
516
517
        Compare the binary representation of structs to decide whether they
518
        are equal or not.
519
520
        Args:
521
            other (GenericStruct): The struct to be compared with.
522
        """
523 1
        return self.pack() == other.pack()
524
525
    @staticmethod
526 1
    def _attr_fits_into_class(attr, cls):
527 1
        if not isinstance(attr, cls):
528
            try:
529 1
                struct.pack(cls._fmt, attr)  # pylint: disable=protected-access
530
            except struct.error:
531
                return False
532
        return True
533
534
    @staticmethod
535
    def _is_pyof_attribute(obj):
536
        """Return True if the object is a kytos attribute.
537
538
        To be a kytos attribute the item must be an instance of either
539
        GenericType or GenericStruct.
540
541 1
        returns:
542 1
            True: if the obj is a kytos attribute
543 1
            False: if the obj is not a kytos attribute
544
        """
545 1
        return isinstance(obj, (GenericType, GenericStruct))
546
547
    def _validate_attributes_type(self):
548
        """Validate the type of each attribute."""
549
        for _attr, _class in self._get_attributes():
550
            if isinstance(_attr, _class):
551
                return True
552
            elif issubclass(_class, GenericType):
553
                if GenericStruct._attr_fits_into_class(_attr, _class):
554
                    return True
555
            elif not isinstance(_attr, _class):
556
                return False
557 1
        return True
558
559
    @classmethod
560
    def get_class_attributes(cls):
561 1
        """Return a generator for class attributes' names and value.
562 1
563 1
        This method strict relies on the PEP 520 (Preserving Class Attribute
564 1
        Definition Order), implemented on Python 3.6. So, if this behaviour
565 1
        changes this whole lib can loose its functionality (since the
566
        attributes order are a strong requirement.) For the same reason, this
567 1
        lib will not work on python versions earlier than 3.6.
568 1
569 1
        .. code-block:: python3
570
571
            for name, value in self.get_class_attributes():
572
                print("attribute name: {}".format(name))
573
                print("attribute type: {}".format(value))
574 1
575
        returns:
576 1
            generator: tuples with attribute name and value.
577
        """
578
        #: see this method docstring for a important notice about the use of
579
        #: cls.__dict__
580
        for name, value in cls.__dict__.items():
581
            # gets only our (kytos) attributes. this ignores methods, dunder
582
            # methods and attributes, and common python type attributes.
583
            if GenericStruct._is_pyof_attribute(value):
584
                yield (name, value)
585
586
    def _get_instance_attributes(self):
587
        """Return a generator for instance attributes' name and value.
588
589
        .. code-block:: python3
590
591
            for _name, _value in self._get_instance_attributes():
592 1
                print("attribute name: {}".format(_name))
593 1
                print("attribute value: {}".format(_value))
594
595 1
        returns:
596 1
            generator: tuples with attribute name and value.
597
        """
598
        for name, value in self.__dict__.items():
599
            if name in map((lambda x: x[0]), self.get_class_attributes()):
600
                yield (name, value)
601
602 1
    def _get_attributes(self):
603
        """Return a generator for instance and class attribute.
604
605
        .. code-block:: python3
606
607
            for instance_attribute, class_attribute in self._get_attributes():
608
                print("Instance Attribute: {}".format(instance_attribute))
609
                print("Class Attribute: {}".format(class_attribute))
610
611
        Returns:
612
            generator: Tuples with instance attribute and class attribute
613
        """
614
        return map((lambda i, c: (i[1], c[1])),
615 1
                   self._get_instance_attributes(),
616 1
                   self.get_class_attributes())
617
618
    def _unpack_attribute(self, name, obj, buff, begin):
619
        attribute = deepcopy(obj)
620
        setattr(self, name, attribute)
621 1
        if not buff:
622
            size = 0
623 1
        else:
624 1
            try:
625 1
                # print('unpacking: ', name, attribute, buff[begin:])
626 1
                attribute.unpack(buff, begin)
627 1
                # print('value: ', attribute)
628
                size = attribute.get_size()
629
            except UnpackException as e:
630
                child_cls = type(self).__name__
631
                msg = '{}.{}; {}'.format(child_cls, name, e)
632
                raise UnpackException(msg)
633 1
        return size
634
635
    def _work_or_pass(self, value, work_func):
636
        if value is None:
637
            return getattr(self, work_func)()
638
        elif isinstance(value, type(self)):
639
            return getattr(value, work_func)()
640
        else:
641
            msg = "{} is not an instance of {}".format(value,
642
                                                       type(self).__name__)
643 1
            raise PackException(msg)
644 1
645 1
    def _get_size(self):
646 1
        return sum(cls_val.get_size(obj_val)
647
                   for obj_val, cls_val in self._get_attributes())
648 1
649
    def get_size(self, value=None):
650
        """Calculate the total struct size in bytes.
651
652
        For each struct attribute, sum the result of each one's ``get_size()``
653
        method.
654
655
        Args:
656
            value: In structs, the user can assign other value instead of a
657
                class' instance.
658
659 1
        Returns:
660
            int: Total number of bytes used by the struct.
661
662
        Raises:
663
            Exception: If the struct is not valid.
664 1
        """
665
        return self._work_or_pass(value, '_get_size')
666
667
    def pack(self, value=None):
668
        """Pack the struct in a binary representation.
669
670
        Iterate over the class attributes, according to the
671
        order of definition, and then convert each attribute to its byte
672
        representation using its own ``pack`` method.
673 1
674
        Returns:
675 1
            bytes: Binary representation of the struct object.
676
677 1
        Raises:
678 1
            :exc:`~.exceptions.ValidationError`: If validation fails.
679 1
        """
680
        return self._work_or_pass(value, '_pack')
681 1
682 1
    def _pack(self):
683
        if not self.is_valid():
684
            error_msg = "Error on validation prior to pack() on class "
685
            error_msg += "{}.".format(type(self).__name__)
686 1
            raise ValidationError(error_msg)
687
        else:
688 1
            message = b''
689
            # pylint: disable=no-member
690
            for instance_attr, class_attr in self._get_attributes():
691 1
                message += class_attr.pack(instance_attr)
692
            return message
693
694
    def unpack(self, buff, offset=0):
695
        """Unpack a binary struct into this object's attributes.
696
697
        Update this object attributes based on the unpacked values of *buff*.
698
        It is an inplace method and it receives the binary data of the struct.
699
700
        Args:
701
            buff (bytes): Binary data package to be unpacked.
702 1
            offset (int): Where to begin unpacking.
703
        """
704
        begin = offset
705
        for name, value in self.get_class_attributes():
706 1
            size = self._unpack_attribute(name, value, buff, begin)
707
            # print('size', size)
708
            begin += size
709
710
    def is_valid(self):
711
        """Check whether all struct attributes in are valid.
712
713
        This method will check whether all struct attributes have a proper
714
        value according to the OpenFlow specification. For instance, if you
715
        have a struct with an attribute of type
716
        :class:`~pyof.foundation.basic_types.UBInt8` and you assign a string
717
        value to it, this method will return False.
718
719
        Returns:
720
            bool: Whether the struct is valid.
721
        """
722
        return True
723
        # pylint: disable=unreachable
724
        return self._validate_attributes_type()
725 1
726 1
727 1
class GenericMessage(GenericStruct):
728
    """Base class that is the foundation for all OpenFlow messages.
729
730
    To add a method that will be used by all messages, write it here.
731
732
    .. note:: A Message on this library context is like a Struct but has a
733
              also a :attr:`header` attribute.
734
    """
735 1
736
    header = None
737
738
    def __init__(self, xid=None):
739
        """Initialize header's xid."""
740
        super().__init__()
741
        if xid is not None:
742
            self.header.xid = xid
743
744
    def __init_subclass__(cls, **kwargs):
745
        if cls.header is None or cls.header.__class__.__name__ != 'Header':
746
            msg = "The header attribute must be implemented on the class "
747 1
            msg += cls.__name__ + "."
748 1
            raise NotImplementedError(msg)
749 1
        super().__init_subclass__(**kwargs)
750 1
751 1
    def _validate_message_length(self):
752
        return self.header.length == self.get_size()
753 1
754
    def is_valid(self):
755
        """Check whether a message is valid or not.
756
757
        This method will validate the Message content. During the validation
758
        process, we check whether the attributes' values are valid according to
759 1
        the OpenFlow specification. Call this method if you want to verify
760
        whether the message is ready to pack.
761
762 1
        Returns:
763
            bool: Whether the message is valid.
764
        """
765
        return True
766
        # pylint: disable=unreachable
767
        return super().is_valid() and self._validate_message_length()
768
769
    def _pack(self):
770
        self.update_header_length()
771
        return super()._pack()
772
773
    def unpack(self, buff, offset=0):
774 1
        """Unpack a binary message into this object's attributes.
775
776 1
        Unpack the binary value *buff* and update this object attributes based
777
        on the results. It is an inplace method and it receives the binary data
778
        of the message **without the header**.
779
780 1
        Args:
781 1
            buff (bytes): Binary data package to be unpacked, without the
782
                header.
783
            offset (int): Where to begin unpacking.
784 1
        """
785 1
        begin = offset
786
        for name, value in self.get_class_attributes():
787 1
            if type(value).__name__ != "Header":
788 1
                size = self._unpack_attribute(name, value, buff, begin)
789
                begin += size
790 1
791
    def update_header_length(self):
792
        """Update the header length attribute based on current message size.
793
794
        When sending an OpenFlow message we need to inform the message length
795
        on the header. This is mandatory.
796
        """
797 1
        self.header.length = self.get_size()
798
799
800 1
class MetaBitMask(type):
801
    """MetaClass to create a special BitMaskEnum type.
802
803
    You probably do not need to use this class. Inherit from
804
    :class:`GenericBitMask` instead.
805
806 1
    This metaclass converts the declared class attributes into elements of an
807 1
    enum. It also replaces the :meth:`__dir__` and :meth:`__getattr__` methods,
808
    so the resulting class will behave as an :class:`~Enum` class (you can
809 1
    access object.ELEMENT and recover either values or names).
810
    """
811
812 1
    def __new__(mcs, name, bases, classdict):
813
        """Convert class attributes into enum elements."""
814
        _enum = OrderedDict([(key, value) for key, value in classdict.items()
815 1
                             if key[0] != '_' and not
816
                             hasattr(value, '__call__') and not
817
                             isinstance(value, property)])
818
        if _enum:
819
            classdict = {key: value for key, value in classdict.items()
820
                         if key[0] == '_' or hasattr(value, '__call__') or
821
                         isinstance(value, property)}
822
            classdict['_enum'] = _enum
823
        return type.__new__(mcs, name, bases, classdict)
824
825
    def __getattr__(cls, name):
826
        return cls._enum[name]
827
828 1
    def __dir__(cls):
829
        res = dir(type(cls)) + list(cls.__dict__.keys())
830
        if cls is not GenericBitMask:
831
            res.extend(cls._enum)
832
        return res
833
834
835
class GenericBitMask(object, metaclass=MetaBitMask):
836
    """Base class for enums that use bitmask values."""
837
838
    def __init__(self, bitmask=None):
839
        """The constructor has the optional parameter below.
840
841
        Args:
842
            bitmask: Bitmask value.
843
        """
844
        self.bitmask = bitmask
845
        self._enum = {}
846
847
    def __str__(self):
848
        return "{}".format(self.bitmask)
849
850
    def __repr__(self):
851
        return "{}({})".format(type(self).__name__, self.bitmask)
852
853
    @property
854
    def names(self):
855
        """List of selected enum names.
856
857
        Returns:
858
            list: Enum names.
859
        """
860
        result = []
861
        for key, value in self.iteritems():
862
            if value & self.bitmask:
863
                result.append(key)
864
        return result
865
866
    def iteritems(self):
867
        """Generator for attributes' name-value pairs.
868
869
        Returns:
870
            generator: Attributes' (name, value) tuples.
871
        """
872
        for key, value in self._enum.items():
873
            yield (key, value)
874