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