Passed
Push — master ( 667973...d237fc )
by Beraldo
01:46
created

build.utils.GenericHello.unpack()   B

Complexity

Conditions 8

Size

Total Lines 43
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
cc 8
eloc 33
nop 2
dl 0
loc 43
ccs 0
cts 30
cp 0
crap 72
rs 7.2213
c 0
b 0
f 0
1
"""of_core utility functions and classes."""
2
3
import struct
4
from collections import OrderedDict
5
6
from pyof.foundation.exceptions import PackException, UnpackException
7
from pyof.v0x01.common.header import Type as OFPTYPE
8
9
from kytos.core import KytosEvent
10
11
12
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
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
def _emit_message(controller, connection, message, direction):
36
    """Emit a KytosEvent for every incoming or outgoing message."""
37
    if direction == 'in':
38
        address_type = 'source'
39
        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
    name = message.header.message_type.name.lower()
47
    hex_version = 'v0x%0.2x' % (message.header.version + 0)
48
    of_event = KytosEvent(
49
        name=f"kytos/of_core.{hex_version}.messages.{direction}.{name}",
50
        content={'message': message,
51
                 address_type: connection})
52
    message_buffer.put(of_event)
53
54
55
def emit_message_in(controller, connection, message):
56
    """Emit a KytosEvent for every incoming message."""
57
    _emit_message(controller, connection, message, 'in')
58
59
60
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
class GenericHello:
66
    """Version agnostic OpenFlow Hello Message."""
67
68
    header_sizes = OrderedDict(
69
        version=1,
70
        type=1,
71
        length=2,
72
        xid=4)
73
74
    elem_type_size = 2
75
    elem_len_size = 2
76
77
    OFPHET_VERSIONBITMAP = 1
78
79
    class GenericHeader:
80
        xid = None
81
        type = None
82
        length = None
83
84
    def __init__(self, *, packet=None, versions=None, xid=None):
85
        """Initialize from a binary packet or from initial versions and xid.
86
87
        Parameters:
88
            packet: binary packet (bytes) to be unpacked and used to
89
                    initialize the message
90
            versions: list of versions used to build the version bitmap
91
            xid: xid to be used in the message
92
93
        """
94
        self.header = self.GenericHeader()
95
        self.header.message_type = OFPTYPE.OFPT_HELLO
96
        if not any((packet, versions)):
97
            raise Exception('either packet or versions must be set.')
98
99
        if packet is not None:
100
            self.unpack(packet)
101
        else:
102
            if xid is None:
103
                self.header.xid = 0
104
105
        if versions is not None:
106
            self.versions = versions
107
            self.header.version = max(versions)
108
        if xid is not None:
109
            self.header.xid = xid
110
111
    def pack(self):
112
        """Encode OpenFlow packet."""
113
        versions = self.versions
114
        xid = self.header.xid
115
        packet_version = max(versions)
116
        if packet_version > 31:
117
            raise PackException
118
        versions_value = 0
119
        for version in versions:
120
            versions_value += (1 << version)
121
        bitmap = versions_value.to_bytes(4, byteorder='big')
122
        version_byte = packet_version.to_bytes(self.header_sizes['version'],
123
                                               byteorder='big')
124
        xid_byte = xid.to_bytes(self.header_sizes['xid'],
125
                                byteorder='big')
126
        packet = version_byte + b'\x00\x00\x10' + xid_byte + \
127
            b'\x00\x01\x00\x08' + bitmap
128
        return packet
129
130
    def unpack(self, packet):
131
        """Decode OpenFlow packet."""
132
        offset = 0
133
        # self.header = self.generic_Header()
134
        for key, size in self.header_sizes.items():
135
            setattr(self.header, key, _unpack_int(packet, offset, size))
136
            offset += size
137
138
        if self.header.type != 0:
139
            raise UnpackException
140
141
        elements = {}
142
        try:
143
            while offset < self.header.length:
144
                elem_type = _unpack_int(
145
                    packet, offset, self.elem_type_size)
146
147
                offset += self.elem_type_size
148
                elem_length = _unpack_int(
149
                    packet, offset, self.elem_len_size)
150
151
                offset += self.elem_len_size
152
                elem_header_size = self.elem_type_size - self.elem_len_size
153
                elem_value_size = elem_length - elem_header_size
154
                elem_value = _unpack_int(
155
                    packet, offset, elem_value_size)
156
157
                elements[elem_type] = elem_value
158
        except IndexError:
159
            raise UnpackException
160
161
        self.elements = elements
162
163
        if self.OFPHET_VERSIONBITMAP in self.elements:
164
            bitmap = self.elements[self.OFPHET_VERSIONBITMAP]
165
            versions = []
166
            for i in range(32):
167
                if ((1 << i) & bitmap) != 0:
168
                    versions.append(i)
169
            self.versions = versions
170
            self.version_bitmap = bitmap
171
        else:
172
            self.versions = None
173
174
175
class NegotiationException(Exception):
176
    """Exception raised when OpenFlow version negotiation failed."""
177
178
    def __str__(self):
179
        return "OF version negotiation failed: " + super().__str__()
180