blob: 8553f6278bd2b5e22b46a2828d8c403009037e11 [file] [log] [blame]
Andreas Wundsamd30c1072013-11-15 13:36:57 -08001# Copyright 2013, Big Switch Networks, Inc.
2#
3# LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
4# the following special exception:
5#
6# LOXI Exception
7#
8# As a special exception to the terms of the EPL, you may distribute libraries
9# generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
10# that copyright and licensing notices generated by LoxiGen are not altered or removed
11# from the LoxiGen Libraries and the notice provided below is (i) included in
12# the LoxiGen Libraries, if distributed in source code form and (ii) included in any
13# documentation for the LoxiGen Libraries, if distributed in binary form.
14#
15# Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
16#
17# You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
18# a copy of the EPL at:
19#
20# http://www.eclipse.org/legal/epl-v10.html
21#
22# Unless required by applicable law or agreed to in writing, software
23# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
24# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
25# EPL for the specific language governing permissions and limitations
26# under the EPL.
27
28from itertools import chain
29import logging
30import re
31import sys
32
33from collections import namedtuple, OrderedDict
34from generic_utils import find, memoize, OrderedSet
35from loxi_ir import ir_offset
36
37logger = logging.getLogger(__name__)
38
39# This module is intended to be imported like this: from loxi_ir import *
40# All public names are prefixed with 'OF'.
41__all__ = [
42 'OFVersion',
43 'OFProtocol',
44 'OFClass',
45 'OFUnifiedClass',
46 'OFDataMember',
47 'OFTypeMember',
48 'OFDiscriminatorMember',
49 'OFLengthMember',
50 'OFFieldLengthMember',
51 'OFPadMember',
52 'OFEnum',
53 'OFEnumEntry'
54]
55
56"""
57One version of the OpenFlow protocol
58@param version Official dotted version number (e.g., "1.0", "1.3")
59@param wire_version Integer wire version (1 for 1.0, 4 for 1.3)
60"""
61class OFVersion(namedtuple("OFVersion", ("version", "wire_version"))):
62 @property
63 @memoize
64 def constant(self):
65 """ return this version as an uppercase string suitable
66 for use as a c constant, e.g., "VERSION_1_3"
67 """
68 return self.constant_version(prefix="VERSION_")
69
70 @property
71 @memoize
72 def short_constant(self):
73 """ return this version as an uppercase string suitable
74 for use as a c constant, e.g., "OF_"
75 """
76 return self.constant_version(prefix="OF_")
77
78 def constant_version(self, prefix="VERSION_"):
79 return prefix + self.version.replace(".", "_")
80
81 def __repr__(self):
82 return "OFVersion(%s)" % self.version
83
84 def __str__(self):
85 return self.version
86
87 def __cmp__(self, other):
88 return cmp(self.wire_version, other.wire_version)
89
90"""
91One version of the OpenFlow protocol
92
93Combination of multiple OFInput objects.
94
95@param wire_version
96@param classes List of OFClass objects
97@param enums List of Enum objects
98"""
99class OFProtocol(namedtuple('OFProtocol', ['version', 'classes', 'enums'])):
100 def __init__(self, version, classes, enums):
101 super(OFProtocol, self).__init__(self, version, classes, enums)
102 assert version is None or isinstance(version, OFVersion)
103
104 def class_by_name(self, name):
105 return find(lambda ofclass: ofclass.name == name, self.classes)
106
107 def enum_by_name(self, name):
108 return find(lambda enum: enum.name == name, self.enums)
109
110"""
111An OpenFlow class
112
113All compound objects like messages, actions, instructions, etc are
114uniformly represented by this class.
115
116The members are in the same order as on the wire.
117
118@param name
119@param superclass_name of this classes' super class
120@param members List of *Member objects
121@param params optional dictionary of parameters
122"""
123class OFClass(namedtuple('OFClass', ['name', 'superclass', 'members', 'virtual', 'params', 'is_fixed_length', 'base_length'])):
124 def __init__(self, *a, **kw):
125 super(OFClass, self).__init__(self, *a, **kw)
126 # Back reference will be added by assignment
127 self.protocol = None
128
129 def member_by_name(self, name):
130 return find(lambda m: hasattr(m, "name") and m.name == name, self.members)
131
132 @property
133 def discriminator(self):
134 return find(lambda m: type(m) == OFDiscriminatorMember, self.members)
135
136 def is_instanceof(self, super_class_name):
137 if self.name == super_class_name:
138 return True
139 elif self.superclass is None:
140 return False
141 else:
142 return self.superclass.is_instanceof(super_class_name)
143
144 def is_subclassof(self, super_class_name):
145 return self.name != super_class_name and self.is_instanceof(super_class_name)
146
147 @property
148 def is_message(self):
149 return self.is_instanceof("of_header")
150
151 @property
152 def is_oxm(self):
153 return self.is_instanceof("of_oxm")
154
155 @property
156 def is_action(self):
157 return self.is_instanceof("of_action")
158
159 @property
160 def is_action_id(self):
161 return self.is_instanceof("of_action_id")
162
163 @property
164 def is_instruction(self):
165 return self.is_instanceof("of_instruction")
166
167 def __hash__(self):
168 return hash((self.name, self.protocol.wire_version if self.protocol else None))
169
170 @property
171 def length(self):
172 if self.is_fixed_length:
173 return self.base_length
174 else:
Andreas Wundsam55ecc3c2013-11-15 16:11:52 -0800175 raise Exception("Not a fixed length class: {}".format(self.name))
Andreas Wundsamd30c1072013-11-15 13:36:57 -0800176
177""" one class unified across openflow versions. Keeps around a map version->versioned_class """
178class OFUnifiedClass(OFClass):
179 def __new__(cls, version_classes, *a, **kw):
180 return super(OFUnifiedClass, cls).__new__(cls, *a, **kw)
181
182 def __init__(self, version_classes, *a, **kw):
183 super(OFUnifiedClass, self).__init__(*a, **kw)
184 self.version_classes = version_classes
185
Andreas Wundsam343abd92013-11-16 13:16:07 -0800186 def class_by_version(self, version):
Andreas Wundsamd30c1072013-11-15 13:36:57 -0800187 return self.version_classes[version]
188
189
190
191""" A mixin for member classes. Keeps around the back reference of_class (for assignment by
192 build_protocol, and additional methods shared across Members. """
193class MemberMixin(object):
194 def __init__(self, *a, **kw):
195 super(MemberMixin, self).__init__(*a, **kw)
196 # Back reference will be added by assignment in build_protocol below
197 self.of_class = None
198
199 @property
200 def length(self):
201 if self.is_fixed_length:
202 return self.base_length
203 else:
204 raise Exception("Not a fixed length member: {}.{} [{}]".format(
205 self.of_class.name,
206 self.name if hasattr("self", name) else "(unnnamed)",
207 type(self).__name__))
208
209"""
210Normal field
211
212@param name
213@param oftype C-like type string
214
215Example: packet_in.buffer_id
216"""
217class OFDataMember(namedtuple('OFDataMember', ['name', 'oftype', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
218 pass
219
220"""
221Field that declares that this is an abstract super-class and
222that the sub classes will be discriminated based on this field.
223E.g., 'type' is the discriminator member of the abstract superclass
224of_action.
225
226@param name
227"""
228class OFDiscriminatorMember (namedtuple('OFDiscriminatorMember', ['name', 'oftype', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
229 pass
230
231"""
232Field used to determine the type of an OpenFlow object
233
234@param name
235@param oftype C-like type string
236@param value Fixed type value
237
238Example: packet_in.type, flow_add._command
239"""
240class OFTypeMember (namedtuple('OFTypeMember', ['name', 'oftype', 'value', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
241 pass
242
243"""
244Field with the length of the containing object
245
246@param name
247@param oftype C-like type string
248
249Example: packet_in.length, action_output.len
250"""
251class OFLengthMember (namedtuple('OFLengthMember', ['name', 'oftype', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
252 pass
253
254"""
255Field with the length of another field in the containing object
256
257@param name
258@param oftype C-like type string
259@param field_name Peer field whose length this field contains
260
261Example: packet_out.actions_len (only usage)
262"""
263class OFFieldLengthMember (namedtuple('OFFieldLengthMember', ['name', 'oftype', 'field_name', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
264 pass
265
266"""
267Zero-filled padding
268
269@param length Length in bytes
270
271Example: packet_in.pad
272"""
273class OFPadMember (namedtuple('OFPadMember', ['pad_length', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
274 pass
275
276"""
277An OpenFlow enumeration
278
279All values are Python ints.
280
281@param name
282@param entries List of OFEnumEntry objects in input order
283@params dict of optional params. Currently defined:
284 - wire_type: the low_level type of the enum values (uint8,...)
285"""
286class OFEnum(namedtuple('OFEnum', ['name', 'entries', 'params'])):
287 def __init__(self, *a, **kw):
288 super(OFEnum, self).__init__(*a, **kw)
289 # Back reference will be added by assignment
290 self.protocol = None
291
292 @property
293 def values(self):
294 return [(e.name, e.value) for e in self.entries]
295
296 @property
297 def is_bitmask(self):
298 return "bitmask" in self.params and self.params['bitmask']
299
300 @property
301 def wire_type(self):
302 return self.params['wire_type'] if 'wire_type' in self.params else self.name
303
304class OFEnumEntry(namedtuple('OFEnumEntry', ['name', 'value', 'params'])):
305 def __init__(self, *a, **kw):
306 super(OFEnumEntry, self).__init__(*a, **kw)
307 # Back reference will be added by assignment
308 self.enum = None
309
310class RedefinedException(Exception):
311 pass
312
313class ClassNotFoundException(Exception):
314 pass
315
316class DependencyCycleException(Exception):
317 pass
318
319def build_protocol(version, ofinputs):
320 name_frontend_classes = OrderedDict()
321 name_frontend_enums = OrderedDict()
322
323 for ofinput in ofinputs:
324 for c in ofinput.classes:
325 name = c.name
326 if name in name_frontend_classes:
327 raise RedefinedException("Error parsing {}. Class {} redefined (already defined in {})"
328 .format(ofinput.filename, name,
329 name_frontend_classes[name][1].filename))
330 else:
331 name_frontend_classes[name] = (c, ofinput)
332 for e in ofinput.enums:
333 name = e.name
334 if name in name_frontend_enums:
335 raise RedefinedException("Error parsing {}. Enum {} redefined (already defined in {})"
336 .format(ofinput.filename, name,
337 name_frontend_enums[name][1].filename))
338 else:
339 name_frontend_enums[name] = (e, ofinput)
340
341 name_enums = {}
342 for fe, _ in name_frontend_enums.values():
343 entries = tuple(OFEnumEntry(name=e.name, value=e.value,
344 params=e.params) for e in fe.entries)
345 enum = OFEnum(name=fe.name,
346 entries=entries,
347 params=fe.params)
348 for e in entries:
349 e.enum = enum
350 name_enums[enum.name] = enum
351
352 name_classes = OrderedDict()
353 build_touch_classes = OrderedSet()
354
355 def convert_member_properties(props):
356 return { name if name != "length" else "pad_length" : value for name, value in props.items() }
357
358 def build_member(of_class, fe_member, length_info):
359 ir_class = globals()[type(fe_member).__name__]
360 member = ir_class(offset = length_info.offset,
361 base_length = length_info.base_length,
362 is_fixed_length=length_info.is_fixed_length,
Andreas Wundsam8a6fbb72013-11-18 19:35:35 -0800363 **convert_member_properties(fe_member._asdict()))
Andreas Wundsamd30c1072013-11-15 13:36:57 -0800364 member.of_class = of_class
365 return member
366
367 def build_class(name):
368 if name in name_classes:
369 return name_classes[name]
370 if name in build_touch_classes:
371 raise DependencyCycleException( "Dependency cycle: {}"
372 .format(" -> ".join(list(build_touch_classes) + [name])))
373 if not name in name_frontend_classes:
374 raise ClassNotFoundException("Class not found: {}".format(name))
375
376 build_touch_classes.add(name)
377
378 fe, _ = name_frontend_classes[name]
379
380 superclass = build_class(fe.superclass) if fe.superclass else None
381
382 # make sure members on which we depend are built first (for calc_length)
383 for m in fe.members:
384 if not hasattr(m, "oftype"):
385 continue
386 for m_name in re.sub(r'_t$', '', m.oftype), m.oftype:
387 logger.debug("Checking {}".format(m_name))
388 if m_name in name_frontend_classes:
389 build_class(m_name)
390
391 base_length, is_fixed_length, member_lengths = \
392 ir_offset.calc_lengths(version, fe, name_classes, name_enums)
393
394 members = []
395 c = OFClass(name=fe.name, superclass=superclass,
396 members=members, virtual=fe.virtual, params=fe.params,
397 is_fixed_length=is_fixed_length, base_length=base_length)
398
399 members.extend( build_member(c, fe_member, member_lengths[fe_member])
400 for fe_member in fe.members)
401
402 name_classes[name] = c
403 build_touch_classes.remove(name)
404 return c
405
406 for name in sorted(name_frontend_classes.keys()):
407 c = build_class(name)
408
409 protocol = OFProtocol(version=version, classes=tuple(name_classes.values()), enums=tuple(name_enums.values()))
410 for e in chain(protocol.classes, protocol.enums):
411 e.protocol = protocol
412 return protocol