Passed
Push — master ( fee64b...f378c6 )
by Humberto
01:57
created

build.utils   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 182
Duplicated Lines 0 %

Test Coverage

Coverage 29.63%

Importance

Changes 0
Metric Value
eloc 123
dl 0
loc 182
ccs 32
cts 108
cp 0.2963
rs 10
c 0
b 0
f 0
wmc 29

4 Methods

Rating   Name   Duplication   Size   Complexity  
A NegotiationException.__str__() 0 2 1
A GenericHello.pack() 0 18 3
B GenericHello.__init__() 0 26 6
B GenericHello.unpack() 0 43 8

5 Functions

Rating   Name   Duplication   Size   Complexity  
A emit_message_in() 0 3 1
A _unpack_int() 0 6 3
A of_slicer() 0 13 3
A emit_message_out() 0 3 1
A _emit_message() 0 18 3
1
"""of_core utility functions and classes."""
2
3 1
import struct
4 1
from collections import OrderedDict
5
6 1
from pyof.foundation.exceptions import PackException, UnpackException
7 1
from pyof.v0x01.common.header import Type as OFPTYPE
8
9 1
from kytos.core import KytosEvent
10
11
12 1
def of_slicer(remaining_data):
13
    """Slice a raw `bytes` instance into OpenFlow packets."""
14
    data_len = len(remaining_data)
15
    pkts = []
16
    while data_len > 3:
17
        length_field = struct.unpack('!H', remaining_data[2:4])[0]
18
        if data_len >= length_field:
19
            pkts.append(remaining_data[:length_field])
20
            remaining_data = remaining_data[length_field:]
21
            data_len = len(remaining_data)
22
        else:
23
            break
24
    return pkts, remaining_data
25
26
27 1
def _unpack_int(packet, offset=0, size=None):
28
    if size is None:
29
        if isinstance(packet, int):
30
            return packet
31
        size = len(packet)
32
    return int.from_bytes(packet[offset:offset + size], byteorder='big')
33
34
35 1
def _emit_message(controller, connection, message, direction):
36
    """Emit a KytosEvent for every incoming or outgoing message."""
37 1
    if direction == 'in':
38 1
        address_type = 'source'
39 1
        message_buffer = controller.buffers.msg_in
40
    elif direction == 'out':
41
        address_type = 'destination'
42
        message_buffer = controller.buffers.msg_out
43
    else:
44
        raise Exception("direction must be 'in' or 'out'")
45
46 1
    name = message.header.message_type.name.lower()
47 1
    hex_version = 'v0x%0.2x' % (message.header.version + 0)
48 1
    of_event = KytosEvent(
49
        name=f"kytos/of_core.{hex_version}.messages.{direction}.{name}",
50
        content={'message': message,
51
                 address_type: connection})
52 1
    message_buffer.put(of_event)
53
54
55 1
def emit_message_in(controller, connection, message):
56
    """Emit a KytosEvent for every incoming message."""
57 1
    _emit_message(controller, connection, message, 'in')
58
59
60 1
def emit_message_out(controller, connection, message):
61
    """Emit a KytosEvent for every outgoing message."""
62
    _emit_message(controller, connection, message, 'out')
63
64
65 1
class GenericHello:
66
    """Version agnostic OpenFlow Hello Message."""
67
68 1
    header_sizes = OrderedDict(
69
        version=1,
70
        type=1,
71
        length=2,
72
        xid=4)
73
74 1
    elem_type_size = 2
75 1
    elem_len_size = 2
76
77 1
    OFPHET_VERSIONBITMAP = 1
78
79 1
    class GenericHeader:
80
        """Generic header for the OpenFlow message."""
81
82 1
        xid = None
83 1
        type = None
84 1
        length = None
85
86 1
    def __init__(self, *, packet=None, versions=None, xid=None):
87
        """Initialize from a binary packet or from initial versions and xid.
88
89
        Parameters:
90
            packet: binary packet (bytes) to be unpacked and used to
91
                    initialize the message
92
            versions: list of versions used to build the version bitmap
93
            xid: xid to be used in the message
94
95
        """
96
        self.header = self.GenericHeader()
97
        self.header.message_type = OFPTYPE.OFPT_HELLO
98
        if not any((packet, versions)):
99
            raise Exception('either packet or versions must be set.')
100
101
        if packet is not None:
102
            self.unpack(packet)
103
        else:
104
            if xid is None:
105
                self.header.xid = 0
106
107
        if versions is not None:
108
            self.versions = versions
109
            self.header.version = max(versions)
110
        if xid is not None:
111
            self.header.xid = xid
112
113 1
    def pack(self):
114
        """Encode OpenFlow packet."""
115
        versions = self.versions
116
        xid = self.header.xid
117
        packet_version = max(versions)
118
        if packet_version > 31:
119
            raise PackException
120
        versions_value = 0
121
        for version in versions:
122
            versions_value += (1 << version)
123
        bitmap = versions_value.to_bytes(4, byteorder='big')
124
        version_byte = packet_version.to_bytes(self.header_sizes['version'],
125
                                               byteorder='big')
126
        xid_byte = xid.to_bytes(self.header_sizes['xid'],
127
                                byteorder='big')
128
        packet = version_byte + b'\x00\x00\x10' + xid_byte + \
129
            b'\x00\x01\x00\x08' + bitmap
130
        return packet
131
132 1
    def unpack(self, packet):
133
        """Decode OpenFlow packet."""
134
        offset = 0
135
        # self.header = self.generic_Header()
136
        for key, size in self.header_sizes.items():
137
            setattr(self.header, key, _unpack_int(packet, offset, size))
138
            offset += size
139
140
        if self.header.type != 0:
141
            raise UnpackException
142
143
        elements = {}
144
        try:
145
            while offset < self.header.length:
146
                elem_type = _unpack_int(
147
                    packet, offset, self.elem_type_size)
148
149
                offset += self.elem_type_size
150
                elem_length = _unpack_int(
151
                    packet, offset, self.elem_len_size)
152
153
                offset += self.elem_len_size
154
                elem_header_size = self.elem_type_size - self.elem_len_size
155
                elem_value_size = elem_length - elem_header_size
156
                elem_value = _unpack_int(
157
                    packet, offset, elem_value_size)
158
159
                elements[elem_type] = elem_value
160
        except IndexError:
161
            raise UnpackException
162
163
        self.elements = elements
164
165
        if self.OFPHET_VERSIONBITMAP in self.elements:
166
            bitmap = self.elements[self.OFPHET_VERSIONBITMAP]
167
            versions = []
168
            for i in range(32):
169
                if ((1 << i) & bitmap) != 0:
170
                    versions.append(i)
171
            self.versions = versions
172
            self.version_bitmap = bitmap
173
        else:
174
            self.versions = None
175
176
177 1
class NegotiationException(Exception):
178
    """Exception raised when OpenFlow version negotiation failed."""
179
180 1
    def __str__(self):
181
        return "OF version negotiation failed: " + super().__str__()
182