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

CustomTLV.__init__()   A

Complexity

Conditions 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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