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

HWAddress.pack()   B

Complexity

Conditions 6

Size

Total Lines 30

Duplication

Lines 30
Ratio 100 %

Code Coverage

Tests 9
CRAP Score 7.6393

Importance

Changes 0
Metric Value
dl 30
loc 30
ccs 9
cts 14
cp 0.6429
c 0
b 0
f 0
rs 7.5384
cc 6
crap 7.6393

3 Methods

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