Passed
Pull Request — master (#445)
by macartur
02:26
created

BinaryData.pack()   A

Complexity

Conditions 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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