Passed
Pull Request — master (#404)
by
unknown
01:53
created

Char   A

Complexity

Total Complexity 7

Size/Duplication

Total Lines 58
Duplicated Lines 37.93 %

Test Coverage

Coverage 75%

Importance

Changes 0
Metric Value
wmc 7
dl 22
loc 58
ccs 18
cts 24
cp 0.75
rs 10
c 0
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
A tringBase.unpack() 10 21 2
B tringBase.pack() 11 22 4

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