Passed
Pull Request — master (#427)
by
unknown
02:08
created

IPAddress   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 85
Duplicated Lines 36.47 %

Test Coverage

Coverage 78.79%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 13
c 1
b 0
f 0
dl 31
loc 85
ccs 26
cts 33
cp 0.7879
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
A get_size() 0 12 1
B pack() 24 31 6
A __init__() 6 15 3
A unpack() 0 18 3

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
"""Basic types used in structures and messages."""
2
3
# System imports
4 1
import struct
5
6
# Local source tree imports
7 1
from pyof.foundation import exceptions
8 1
from pyof.foundation.base import GenericStruct, GenericType
9
10
# Third-party imports
11
12 1
__all__ = ('BinaryData', 'Char', 'ConstantTypeList', 'FixedTypeList',
13
           'IPAddress', 'DPID', 'HWAddress', 'Pad', 'UBInt8', 'UBInt16',
14
           'UBInt32', 'UBInt64')
15
16
17 1
class Pad(GenericType):
18
    """Class for padding attributes."""
19
20 1
    _fmt = ''
21
22 1
    def __init__(self, length=None):
23
        """Pad up to ``length``, in bytes.
24
25
        Args:
26
            length (int): Total length, in bytes.
27
            value (int): Ignored, always 0.
28
        """
29 1
        super().__init__()
30 1
        if length is None:
31
            length = 0
32 1
        self._length = length
33
34 1
    def __repr__(self):
35
        return "{}({})".format(type(self).__name__, self._length)
36
37 1
    def __str__(self):
38
        return '0' * self._length
39
40 1
    def get_size(self, value=None):
41
        """Return the type size in bytes.
42
43
        Args:
44
            value (int): In structs, the user can assign other value instead of
45
                this class' instance. Here, in such cases, ``self`` is a class
46
                attribute of the struct.
47
48
        Returns:
49
            int: Size in bytes.
50
        """
51 1
        return self._length
52
53 1
    def unpack(self, buff, offset=0):
54
        """Unpack *buff* into this object.
55
56
        Do nothing, since the _length is already defined and it is just a Pad.
57
        Keep buff and offset just for compability with other unpack methods.
58
59
        Args:
60
            buff: Buffer where data is located.
61
            offset (int): Where data stream begins.
62
        """
63 1
        pass
64
65 1
    def pack(self, value=None):
66
        """Pack the object.
67
68
        Args:
69
            value (int): In structs, the user can assign other value instead of
70
                this class' instance. Here, in such cases, ``self`` is a class
71
                attribute of the struct.
72
73
        Returns:
74
            bytes: the byte 0 (zero) *length* times.
75
        """
76 1
        return b'\x00' * self._length
77
78
79 1
class UBInt8(GenericType):
80
    """Format character for an Unsigned Char.
81
82
    Class for an 8-bit (1-byte) Unsigned Integer.
83
    """
84
85 1
    _fmt = "!B"
86
87
88 1
class UBInt16(GenericType):
89
    """Format character for an Unsigned Short.
90
91
    Class for an 16-bit (2-byte) Unsigned Integer.
92
    """
93
94 1
    _fmt = "!H"
95
96
97 1
class UBInt32(GenericType):
98
    """Format character for an Unsigned Int.
99
100
    Class for an 32-bit (4-byte) Unsigned Integer.
101
    """
102
103 1
    _fmt = "!I"
104
105
106 1
class UBInt64(GenericType):
107
    """Format character for an Unsigned Long Long.
108
109
    Class for an 64-bit (8-byte) Unsigned Integer.
110
    """
111
112 1
    _fmt = "!Q"
113
114
115 1
class DPID(GenericType):
116
    """DataPath ID. Identifies a switch."""
117
118 1
    _fmt = "!8B"
119
120 1
    def __init__(self, value=None):
121
        """Create an instance and optionally set its dpid value.
122
123
        Args:
124
            dpid (str): String with DPID value. ('00:00:00:00:00:00:00:00').
125
        """
126 1
        if value is None:
127 1
            value = '00:00:00:00:00:00:00:00'
128 1
        super().__init__(value)
129
130 1
    def __str__(self):
131
        return self._value
132
133 1
    @property
134
    def value(self):
135
        """Return dpid value.
136
137
        Returns:
138
            str: DataPath ID stored by DPID class.
139
        """
140
        return self._value
141
142 1
    def pack(self, value=None):
143
        """Pack the value as a binary representation.
144
145
        Returns:
146
            bytes: The binary representation.
147
148
        Raises:
149
            struct.error: If the value does not fit the binary format.
150
        """
151 1
        if isinstance(value, type(self)):
152 1
            return value.pack()
153 1
        if value is None:
154 1
            value = self._value
155 1
        return struct.pack('!8B', *[int(v, 16) for v in value.split(':')])
156
157 1
    def unpack(self, buff, offset=0):
158
        """Unpack a binary message into this object's attributes.
159
160
        Unpack the binary value *buff* and update this object attributes based
161
        on the results.
162
163
        Args:
164
            buff (bytes): Binary data package to be unpacked.
165
            offset (int): Where to begin unpacking.
166
167
        Raises:
168
            Exception: If there is a struct unpacking error.
169
        """
170 1
        begin = offset
171 1
        hexas = []
172 1
        while begin < offset + 8:
173 1
            number = struct.unpack("!B", buff[begin:begin+1])[0]
174 1
            hexas.append("%.2x" % number)
175 1
            begin += 1
176 1
        self._value = ':'.join(hexas)
177
178
179 1
class Char(GenericType):
180
    """Build a double char type according to the length."""
181
182 1
    def __init__(self, value=None, length=0):
183
        """The constructor takes the optional parameters below.
184
185
        Args:
186
            value: The character to be build.
187
            length (int): Character size.
188 View Code Duplication
        """
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
189 1
        super().__init__(value)
190 1
        self.length = length
191 1
        self._fmt = '!{}{}'.format(self.length, 's')
192
193 1
    def pack(self, value=None):
194
        """Pack the value as a binary representation.
195
196
        Returns:
197
            bytes: The binary representation.
198
199
        Raises:
200
            struct.error: If the value does not fit the binary format.
201
        """
202 1
        if isinstance(value, type(self)):
203 1
            return value.pack()
204
205 1
        try:
206 1
            if value is None:
207 1
                value = self.value
208 1
            packed = struct.pack(self._fmt, bytes(value, 'ascii'))
209 1
            return packed[:-1] + b'\0'  # null-terminated
210
        except struct.error as err:
211
            msg = "Char Pack error. "
212
            msg += "Class: {}, struct error: {} ".format(type(value).__name__,
213
                                                         err)
214
            raise exceptions.PackException(msg)
215
216 1
    def unpack(self, buff, offset=0):
217
        """Unpack a binary message into this object's attributes.
218
219
        Unpack the binary value *buff* and update this object attributes based
220
        on the results.
221
222
        Args:
223
            buff (bytes): Binary data package to be unpacked.
224
            offset (int): Where to begin unpacking.
225
226
        Raises:
227
            Exception: If there is a struct unpacking error.
228
        """
229 1
        try:
230 1
            begin = offset
231 1
            end = begin + self.length
232 1
            unpacked_data = struct.unpack(self._fmt, buff[begin:end])[0]
233
        except struct.error:
234
            raise Exception("%s: %s" % (offset, buff))
235
236 1
        self._value = unpacked_data.decode('ascii').rstrip('\0')
237
238
239 1
class IPAddress(GenericType):
240
    """Defines a IP address."""
241
242 1
    netmask = UBInt32()
243 1
    max_prefix = UBInt32(32)
244
245 1
    def __init__(self, value=None):
246
        """The constructor takes the parameters below.
247
248
        Args:
249
            address (str): IP Address using ipv4. Defaults to '0.0.0.0/32'
250
        """
251 1
        if value is None:
252 1
            value = "0.0.0.0/32"
253 1
        if value.find('/') >= 0:
254 1 View Code Duplication
            value, netmask = value.split('/')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
255
        else:
256 1
            netmask = 32
257
258 1
        super().__init__(value)
259 1
        self.netmask = int(netmask)
260
261 1
    def pack(self, value=None):
262
        """Pack the value as a binary representation.
263
264
        If the value is None the self._value will be used to pack.
265
266
        Args:
267
            value (str): IP Address with ipv4 format.
268
269
        Returns:
270
            bytes: The binary representation.
271
272
        Raises:
273
            struct.error: If the value does not fit the binary format.
274
        """
275 1
        if isinstance(value, type(self)):
276 1
            return value.pack()
277
278 1
        if value is None:
279 1
            value = self._value
280
281 1
        if value.find('/') >= 0:
282
            value = value.split('/')[0]
283
284 1
        try:
285 1
            value = value.split('.')
286 1
            return struct.pack('!4B', *[int(x) for x in value])
287
        except struct.error as err:
288
            msg = "IPAddress error. "
289
            msg += "Class: {}, struct error: {} ".format(type(value).__name__,
290
                                                         err)
291
            raise exceptions.PackException(msg)
292
293 1
    def unpack(self, buff, offset=0):
294
        """Unpack a binary message into this object's attributes.
295
296
        Unpack the binary value *buff* and update this object attributes based
297
        on the results.
298
299
        Args:
300
            buff (bytes): Binary data package to be unpacked.
301
            offset (int): Where to begin unpacking.
302
303
        Raises:
304
            Exception: If there is a struct unpacking error.
305
        """
306 1
        try:
307 1
            unpacked_data = struct.unpack('!4B', buff[offset:offset+4])
308 1
            self._value = '.'.join([str(x) for x in unpacked_data])
309
        except struct.error as e:
310
            raise exceptions.UnpackException('%s; %s: %s' % (e, offset, buff))
311
312 1
    def get_size(self, value=None):
313
        """Return the ip address size in bytes.
314
315
        Args:
316
            value: In structs, the user can assign other value instead of
317
                this class' instance. Here, in such cases, ``self`` is a class
318
                attribute of the struct.
319
320
        Returns:
321
            int: The address size in bytes.
322
        """
323 1
        return 4
324
325
326 1
class HWAddress(GenericType):
327
    """Defines a hardware address."""
328
329 1
    def __init__(self, value=None):  # noqa
330
        """The constructor takes the parameters below.
331 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
332
        Args:
333
            value (bytes): Hardware address. Defaults to
334
                '00:00:00:00:00:00'.
335
        """
336 1
        if value is None:
337 1
            value = '00:00:00:00:00:00'
338 1
        super().__init__(value)
339
340 1
    def pack(self, value=None):
341
        """Pack the value as a binary representation.
342
343
        If the passed value (or the self._value) is zero (int), then the pack
344
        will assume that the value to be packed is '00:00:00:00:00:00'.
345
346
        Returns
347
            bytes: The binary representation.
348
349
        Raises:
350
            struct.error: If the value does not fit the binary format.
351
        """
352 1
        if isinstance(value, type(self)):
353 1
            return value.pack()
354
355 1
        if value is None:
356 1
            value = self._value
357
358 1
        if value == 0:
359
            value = '00:00:00:00:00:00'
360
361 1
        value = value.split(':')
362
363 1
        try:
364 1
            return struct.pack('!6B', *[int(x, 16) for x in value])
365
        except struct.error as err:
366
            msg = "HWAddress error. "
367
            msg += "Class: {}, struct error: {} ".format(type(value).__name__,
368
                                                         err)
369
            raise exceptions.PackException(msg)
370
371 1
    def unpack(self, buff, offset=0):
372
        """Unpack a binary message into this object's attributes.
373
374
        Unpack the binary value *buff* and update this object attributes based
375
        on the results.
376
377
        Args:
378
            buff (bytes): Binary data package to be unpacked.
379
            offset (int): Where to begin unpacking.
380
381
        Raises:
382
            Exception: If there is a struct unpacking error.
383
        """
384 1
        def _int2hex(n):
385 1
            return "{0:0{1}x}".format(n, 2)
386
387 1
        try:
388 1
            unpacked_data = struct.unpack('!6B', buff[offset:offset+6])
389
        except struct.error as e:
390
            raise exceptions.UnpackException('%s; %s: %s' % (e, offset, buff))
391
392 1
        transformed_data = ':'.join([_int2hex(x) for x in unpacked_data])
393 1
        self._value = transformed_data
394
395 1
    def get_size(self, value=None):
396
        """Return the address size in bytes.
397
398
        Args:
399
            value: In structs, the user can assign other value instead of
400
                this class' instance. Here, in such cases, ``self`` is a class
401
                attribute of the struct.
402
403
        Returns:
404
            int: The address size in bytes.
405
        """
406 1
        return 6
407
408 1
    def is_broadcast(self):
409
        """Return true if the value is a broadcast address. False otherwise."""
410
        return self.value == 'ff:ff:ff:ff:ff:ff'
411
412
413 1
class BinaryData(GenericType):
414
    """Class to create objects that represent binary data.
415
416
    This is used in the ``data`` attribute from
417
    :class:`~pyof.v0x01.asynchronous.packet_in.PacketIn` and
418
    :class:`~pyof.v0x01.controller2switch.packet_out.PacketOut` messages.
419
    Both the :meth:`pack` and :meth:`unpack` methods will return the
420
    binary data itself. :meth:`get_size` method will
421
    return the size of the instance using Python's :func:`len`.
422
    """
423
424 1
    def __init__(self, value=b''):  # noqa
425
        """The constructor takes the parameter below.
426
427
        Args:
428
            value (bytes): The binary data. Defaults to an empty value.
429
430
        Raises:
431
            ValueError: If given value is not bytes.
432
        """
433 1
        if not isinstance(value, bytes):
434 1
            raise ValueError('BinaryData must contain bytes.')
435 1
        super().__init__(value)
436
437 1
    def pack(self, value=None):
438
        """Pack the value as a binary representation.
439
440
        Returns:
441
            bytes: The binary representation.
442
443
        Raises:
444
            :exc:`~.exceptions.NotBinaryData`: If value is not :class:`bytes`.
445
        """
446 1
        if isinstance(value, type(self)):
447 1
            return value.pack()
448
449 1
        if value is None:
450 1
            value = self._value
451
452 1
        if value:
453 1
            if isinstance(value, bytes):
454 1
                return value
455 1
            raise ValueError('BinaryData must contain bytes.')
456
457 1
        return b''
458
459 1
    def unpack(self, buff, offset=0):
460
        """Unpack a binary message into this object's attributes.
461
462
        Unpack the binary value *buff* and update this object attributes based
463
        on the results. Since the *buff* is binary data, no conversion is done.
464
465
        Args:
466
            buff (bytes): Binary data package to be unpacked.
467
            offset (int): Where to begin unpacking.
468
        """
469 1
        self._value = buff[offset:]
470
471 1
    def get_size(self, value=None):
472
        """Return the size in bytes.
473
474
        Args:
475
            value (bytes): In structs, the user can assign other value instead
476
                of this class' instance. Here, in such cases, ``self`` is a
477
                class attribute of the struct.
478
479
        Returns:
480
            int: The address size in bytes.
481
        """
482 1
        if value is None:
483 1
            return len(self._value)
484 1
        elif hasattr(value, 'get_size'):
485 1
            return value.get_size()
486
487 1
        return len(value)
488
489
490 1
class TypeList(list, GenericStruct):
491
    """Base class for lists that store objects of one single type."""
492
493 1
    def __init__(self, items):
494
        """Initialize the list with one item or a list of items.
495
496
        Args:
497
            items (iterable, ``pyof_class``): Items to be stored.
498
        """
499 1
        super().__init__()
500 1
        if isinstance(items, list):
501 1
            self.extend(items)
502 1
        elif items:
503
            self.append(items)
504
505 1
    def extend(self, items):
506
        """Extend the list by adding all items of ``items``.
507
508
        Args:
509
            items (iterable): Items to be added to the list.
510
511
        Raises:
512
            :exc:`~.exceptions.WrongListItemType`: If an item has an unexpected
513
                type.
514
        """
515 1
        for item in items:
516 1
            self.append(item)
517
518 1
    def pack(self, value=None):
519
        """Pack the value as a binary representation.
520
521
        Returns:
522
            bytes: The binary representation.
523
        """
524 1
        if isinstance(value, type(self)):
525 1
            return value.pack()
526
527 1
        if value is None:
528 1
            value = self
529
        else:
530 1
            container = type(self)(items=None)
531 1
            container.extend(value)
532 1
            value = container
533
534 1
        bin_message = b''
535 1
        try:
536 1
            for item in value:
537 1
                bin_message += item.pack()
538 1
            return bin_message
539
        except exceptions.PackException as err:
540
            msg = "{} pack error: {}".format(type(self).__name__, err)
541
            raise exceptions.PackException(msg)
542
543 1
    def unpack(self, buff, item_class, offset=0):
544
        """Unpack the elements of the list.
545
546
        This unpack method considers that all elements have the same size.
547
        To use this class with a pyof_class that accepts elements with
548
        different sizes, you must reimplement the unpack method.
549
550
        Args:
551
            buff (bytes): The binary data to be unpacked.
552
            item_class (:obj:`type`): Class of the expected items on this list.
553
            offset (int): If we need to shift the beginning of the data.
554
        """
555 1
        begin = offset
556 1
        limit_buff = len(buff)
557
558 1
        while begin < limit_buff:
559 1
            item = item_class()
560 1
            item.unpack(buff, begin)
561 1
            self.append(item)
562 1
            begin += item.get_size()
563
564 1
    def get_size(self, value=None):
565
        """Return the size in bytes.
566
567
        Args:
568
            value: In structs, the user can assign other value instead of
569
                this class' instance. Here, in such cases, ``self`` is a class
570
                attribute of the struct.
571
572
        Returns:
573
            int: The size in bytes.
574
        """
575 1
        if value is None:
576 1
            if not self:
577
                # If this is a empty list, then returns zero
578 1
                return 0
579 1
            elif issubclass(type(self[0]), GenericType):
580
                # If the type of the elements is GenericType, then returns the
581
                # length of the list multiplied by the size of the GenericType.
582
                return len(self) * self[0].get_size()
583
584
            # Otherwise iter over the list accumulating the sizes.
585 1
            return sum(item.get_size() for item in self)
586
587 1
        return type(self)(value).get_size()
588
589 1
    def __str__(self):
590
        """Human-readable object representantion."""
591
        return "{}".format([str(item) for item in self])
592
593
594 1
class FixedTypeList(TypeList):
595
    """A list that stores instances of one pyof class."""
596
597 1
    _pyof_class = None
598
599 1
    def __init__(self, pyof_class, items=None):
600
        """The constructor parameters follows.
601
602
        Args:
603
            pyof_class (:obj:`type`): Class of the items to be stored.
604
            items (iterable, ``pyof_class``): Items to be stored.
605
        """
606 1
        self._pyof_class = pyof_class
607 1
        super().__init__(items)
608
609 1
    def append(self, item):
610
        """Append one item to the list.
611
612
        Args:
613
            item: Item to be appended. Its type must match the one defined in
614
                the constructor.
615
616
        Raises:
617
            :exc:`~.exceptions.WrongListItemType`: If the item has a different
618
                type than the one specified in the constructor.
619
        """
620 1
        if isinstance(item, list):
621
            self.extend(item)
622 1
        elif issubclass(item.__class__, self._pyof_class):
623 1
            list.append(self, item)
624
        else:
625
            raise exceptions.WrongListItemType(item.__class__.__name__,
626
                                               self._pyof_class.__name__)
627
628 1
    def insert(self, index, item):
629
        """Insert an item at the specified index.
630
631
        Args:
632
            index (int): Position to insert the item.
633
            item: Item to be inserted. It must have the type specified in the
634
                constructor.
635
636
        Raises:
637
            :exc:`~.exceptions.WrongListItemType`: If the item has a different
638
                type than the one specified in the constructor.
639
        """
640
        if issubclass(item.__class__, self._pyof_class):
641
            list.insert(self, index, item)
642
        else:
643
            raise exceptions.WrongListItemType(item.__class__.__name__,
644
                                               self._pyof_class.__name__)
645
646 1
    def unpack(self, buff, offset=0):
647
        """Unpack the elements of the list.
648
649
        This unpack method considers that all elements have the same size.
650
        To use this class with a pyof_class that accepts elements with
651
        different sizes, you must reimplement the unpack method.
652
653
        Args:
654
            buff (bytes): The binary data to be unpacked.
655
            offset (int): If we need to shift the beginning of the data.
656
        """
657 1
        super().unpack(buff, self._pyof_class, offset)
658
659
660 1
class ConstantTypeList(TypeList):
661
    """List that contains only objects of the same type (class).
662
663
    The types of all items are expected to be the same as the first item's.
664
    Otherwise, :exc:`~.exceptions.WrongListItemType` is raised in many
665
    list operations.
666
    """
667
668 1
    def __init__(self, items=None):  # noqa
669
        """The contructor can contain the items to be stored.
670
671
        Args:
672
            items (iterable, :class:`object`): Items to be stored.
673
674
        Raises:
675
            :exc:`~.exceptions.WrongListItemType`: If an item has a different
676
                type than the first item to be stored.
677
        """
678
        super().__init__(items)
679
680 1
    def append(self, item):
681
        """Append one item to the list.
682
683
        Args:
684
            item: Item to be appended.
685
686
        Raises:
687
            :exc:`~.exceptions.WrongListItemType`: If an item has a different
688
                type than the first item to be stored.
689
        """
690
        if isinstance(item, list):
691
            self.extend(item)
692
        elif not self:
693
            list.append(self, item)
694
        elif item.__class__ == self[0].__class__:
695
            list.append(self, item)
696
        else:
697
            raise exceptions.WrongListItemType(item.__class__.__name__,
698
                                               self[0].__class__.__name__)
699
700 1
    def insert(self, index, item):
701
        """Insert an item at the specified index.
702
703
        Args:
704
            index (int): Position to insert the item.
705
            item: Item to be inserted.
706
707
        Raises:
708
            :exc:`~.exceptions.WrongListItemType`: If an item has a different
709
                type than the first item to be stored.
710
        """
711
        if not self:
712
            list.append(self, item)
713
        elif item.__class__ == self[0].__class__:
714
            list.insert(self, index, item)
715
        else:
716
            raise exceptions.WrongListItemType(item.__class__.__name__,
717
                                               self[0].__class__.__name__)
718