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

BinaryData._get_size()   A

Complexity

Conditions 1

Size

Total Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 2
rs 10
ccs 1
cts 1
cp 1
cc 1
crap 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
331
        if hasattr(value, 'pack') and callable(value.pack):
332
            value = value.pack()
333
334
        if not isinstance(value, bytes):
335
            msg = 'BinaryData must contain bytes or have pack method; '
336
            msg += 'Received type {} value: {}'.format(type(value), value)
337
            raise ValueError(msg)
338
        super().__init__(value, enum_ref=enum_ref)
339
340 1
    def _pack(self):
341 1
        return self._value
342
343 1
    def unpack(self, buff, offset=0):
344 1
        """Unpack a binary message into this object's attributes.
345
346 1
        Unpack the binary value *buff* and update this object attributes based
347
        on the results. Since the *buff* is binary data, no conversion is done.
348
349 1
        All the data in the buffer from the offset forward will be used,
350
        so the buffer must be truncated using the desired size before passing
351 1
        it to BinaryData.
352 1
353
        Args:
354
            buff (bytes): Binary data package to be unpacked.
355
            offset (int): Where to begin unpacking.
356
        """
357
        self._value = buff[offset:]
358
359 1
    def _get_size(self):
360
        return len(self._value)
361
362
363
class TypeList(GenericStruct, list):
364
    """Base class for lists that store objects of one single type."""
365
366
    def __init__(self, items):
367
        """Initialize the list with one item or a list of items.
368
369
        Args:
370
            items (iterable, ``pyof_class``): Items to be stored.
371
        """
372 1
        super().__init__()
373 1
        if isinstance(items, list):
374
            self.extend(items)
375 1
        elif items:
376 1
            self.append(items)
377
378
    def extend(self, items):
379
        """Extend the list by adding all items of ``items``.
380 1
381 1
        Args:
382
            items (iterable): Items to be added to the list.
383 1
384
        Raises:
385
            :exc:`~.exceptions.WrongListItemType`: If an item has an unexpected
386
                type.
387
        """
388
        for item in items:
389
            self.append(item)
390
391
    def _pack(self):
392
        bin_message = b''
393
        try:
394 1
            for item in self:
395
                bin_message += item._pack()
396 1
            return bin_message
397
        except exceptions.PackException as err:
398
            msg = "{} pack error: {}".format(type(self).__name__, err)
399
            raise exceptions.PackException(msg)
400
401 1
    def unpack(self, buff, item_class, offset=0):
402
        """Unpack the elements of the list.
403
404
        This unpack method considers that all elements are of the same type.
405
        To use this class with a pyof_class that accepts elements with
406
        different classes, you must reimplement the unpack method.
407
408
        Args:
409
            buff (bytes): The binary data to be unpacked.
410 1
            item_class (:obj:`type`): Class of the expected items on this list.
411
            offset (int): If we need to shift the beginning of the data.
412
        """
413
        begin = offset
414
        limit_buff = len(buff)
415
416 1
        while begin < limit_buff:
417
            item = item_class()
418 1
            item.unpack(buff, begin)
419
            self.append(item)
420
            begin += item.get_size()
421
422
    def _get_size(self):
423
        if not self:
424
            # If this is a empty list, then returns zero
425
            return 0
426
        elif issubclass(type(self[0]), GenericType):
427 1
            # If the type of the elements is GenericType, then returns the
428 1
            # length of the list multiplied by the size of the GenericType.
429
            return len(self) * self[0].get_size()
430 1
431 1
        # Otherwise iter over the list accumulating the sizes.
432
        return sum(item.get_size() for item in self)
433 1
434 1
    def __str__(self):
435
        """Human-readable object representantion."""
436 1
        return "{}".format([str(item) for item in self])
437
438 1
439
class FixedTypeList(TypeList):
440
    """A list that stores instances of one pyof class."""
441
442
    _pyof_class = None
443
444
    def __init__(self, pyof_class, items=None):
445
        """The constructor parameters follows.
446
447
        Args:
448 1
            pyof_class (:obj:`type`): Class of the items to be stored.
449
            items (iterable, ``pyof_class``): Items to be stored.
450 1
        """
451
        self._pyof_class = pyof_class
452
        super().__init__(items)
453
454
    def append(self, item):
455
        """Append one item to the list.
456
457
        Args:
458
            item: Item to be appended. Its type must match the one defined in
459
                the constructor.
460
461 1
        Raises:
462 1
            :exc:`~.exceptions.WrongListItemType`: If the item has a different
463 1
                type than the one specified in the constructor.
464 1
        """
465
        if isinstance(item, list):
466 1
            self.extend(item)
467
        elif issubclass(item.__class__, self._pyof_class):
468
            list.append(self, item)
469 1
        else:
470
            raise exceptions.WrongListItemType(item.__class__.__name__,
471
                                               self._pyof_class.__name__)
472 1
473
    def insert(self, index, item):
474
        """Insert an item at the specified index.
475
476
        Args:
477
            index (int): Position to insert the item.
478 1
            item: Item to be inserted. It must have the type specified in the
479 1
                constructor.
480 1
481 1
        Raises:
482
            :exc:`~.exceptions.WrongListItemType`: If the item has a different
483
                type than the one specified in the constructor.
484 1
        """
485
        if issubclass(item.__class__, self._pyof_class):
486
            list.insert(self, index, item)
487
        else:
488
            raise exceptions.WrongListItemType(item.__class__.__name__,
489
                                               self._pyof_class.__name__)
490
491
    def unpack(self, buff, offset=0):
492
        """Unpack the elements of the list.
493
494 1
        This unpack method considers that all elements have the same size.
495 1
        To use this class with a pyof_class that accepts elements with
496
        different sizes, you must reimplement the unpack method.
497 1
498
        Args:
499
            buff (bytes): The binary data to be unpacked.
500
            offset (int): If we need to shift the beginning of the data.
501
        """
502
        super().unpack(buff, self._pyof_class, offset)
503 1
504 1
505
class ConstantTypeList(TypeList):
506 1
    """List that contains only objects of the same type (class).
507 1
508
    The types of all items are expected to be the same as the first item's.
509 1
    Otherwise, :exc:`~.exceptions.WrongListItemType` is raised in many
510 1
    list operations.
511 1
    """
512
513 1
    def __init__(self, items=None):  # noqa
514 1
        """The contructor can contain the items to be stored.
515 1
516 1
        Args:
517 1
            items (iterable, :class:`object`): Items to be stored.
518
519
        Raises:
520
            :exc:`~.exceptions.WrongListItemType`: If an item has a different
521
                type than the first item to be stored.
522 1
        """
523
        super().__init__(items)
524
525
    def append(self, item):
526
        """Append one item to the list.
527
528
        Args:
529
            item: Item to be appended.
530
531
        Raises:
532
            :exc:`~.exceptions.WrongListItemType`: If an item has a different
533
                type than the first item to be stored.
534 1
        """
535 1
        if isinstance(item, list):
536
            self.extend(item)
537 1
        elif not self:
538 1
            list.append(self, item)
539 1
        elif item.__class__ == self[0].__class__:
540 1
            list.append(self, item)
541 1
        else:
542
            raise exceptions.WrongListItemType(item.__class__.__name__,
543 1
                                               self[0].__class__.__name__)
544
545
    def insert(self, index, item):
546
        """Insert an item at the specified index.
547
548
        Args:
549
            index (int): Position to insert the item.
550
            item: Item to be inserted.
551
552
        Raises:
553
            :exc:`~.exceptions.WrongListItemType`: If an item has a different
554 1
                type than the first item to be stored.
555 1
        """
556
        if not self:
557 1
            list.append(self, item)
558 1
        elif item.__class__ == self[0].__class__:
559
            list.insert(self, index, item)
560
        else:
561
            raise exceptions.WrongListItemType(item.__class__.__name__,
562
                                               self[0].__class__.__name__)
563
564 1
565
def get_custom_tlv_class(type_size=3, length_size=1):
566 1
    """ Generate a CUstomTLV class
567
568 1
    Create a CustomTLV class with the defined number of bytes for type and
569
    length fields.
570
571
    Args:
572
        type_size (int): length in bytes for the type field of the TLV
573 1
        length_size (int): length in bytes for the length field of the TLV
574
    """
575
576 1
    size_classes = {1: UBInt8,
577
                    2: UBInt16,
578 1
                    3: UBInt24,
579
                    4: UBInt32}
580
581
    custom_type = size_classes[type_size]
582
    custom_length = size_classes[length_size]
583
584
    class CustomTLV(GenericStruct):
585 1
        """ a compact TLV class
586 1
587
        Args:
588 1
            tlv_type: type field of the TLV
589
            tlv_value: length field of the TLV
590
        """
591
        tlv_type = custom_type()
592
        tlv_length = custom_length()
593
        tlv_value = BinaryData()
594
595
        def __init__(self, tlv_type=None, tlv_value=None):
596
            super().__init__()
597
            self.tlv_type = tlv_type
598
            self.tlv_value = tlv_value
599 1
            self._update_tlv_length()
600
601 1
        def _update_tlv_length(self):
602 1
            cls = type(self)
603
            self.tlv_length = (type(cls.tlv_value)(self.tlv_value)).get_size()
604
605
        def _pack(self):
606
            self._update_tlv_length()
607 1
            return super()._pack()
608
609
        def unpack(self, buff, offset=0):
610
            """Unpack the buffer into a custom TLV
611
612
            Args:
613
                buff (bytes): The binary data to be unpacked.
614
                offset (int): If we need to shift the beginning of the data.
615
            """
616
617
            begin = offset
618
            for name, value in list(self.get_class_attributes())[:-1]:
619
                size = self._unpack_attribute(name, value, buff, begin)
620
                begin += size
621
            self._unpack_attribute('tlv_value', type(self).tlv_value,
622
                                   buff[:begin + self.tlv_length],
623
                                   begin)
624
625 1
    return CustomTLV
626
627
628
CustomTLV_24_8 = get_custom_tlv_class(type_size=3, length_size=1)
629