blob: 6fae92b18900646d351a07ffced17c1eec7cfc6d [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
Rich Lane7bc23772013-11-29 17:47:46 -080036import loxi_front_end.frontend_ir as frontend_ir
Andreas Wundsamd30c1072013-11-15 13:36:57 -080037
38logger = logging.getLogger(__name__)
39
40# This module is intended to be imported like this: from loxi_ir import *
41# All public names are prefixed with 'OF'.
42__all__ = [
43 'OFVersion',
44 'OFProtocol',
45 'OFClass',
46 'OFUnifiedClass',
47 'OFDataMember',
48 'OFTypeMember',
49 'OFDiscriminatorMember',
50 'OFLengthMember',
51 'OFFieldLengthMember',
52 'OFPadMember',
53 'OFEnum',
54 'OFEnumEntry'
55]
56
57"""
58One version of the OpenFlow protocol
59@param version Official dotted version number (e.g., "1.0", "1.3")
60@param wire_version Integer wire version (1 for 1.0, 4 for 1.3)
61"""
62class OFVersion(namedtuple("OFVersion", ("version", "wire_version"))):
63 @property
64 @memoize
65 def constant(self):
66 """ return this version as an uppercase string suitable
67 for use as a c constant, e.g., "VERSION_1_3"
68 """
69 return self.constant_version(prefix="VERSION_")
70
71 @property
72 @memoize
73 def short_constant(self):
74 """ return this version as an uppercase string suitable
75 for use as a c constant, e.g., "OF_"
76 """
77 return self.constant_version(prefix="OF_")
78
79 def constant_version(self, prefix="VERSION_"):
80 return prefix + self.version.replace(".", "_")
81
82 def __repr__(self):
83 return "OFVersion(%s)" % self.version
84
85 def __str__(self):
86 return self.version
87
88 def __cmp__(self, other):
89 return cmp(self.wire_version, other.wire_version)
90
91"""
92One version of the OpenFlow protocol
93
94Combination of multiple OFInput objects.
95
96@param wire_version
97@param classes List of OFClass objects
98@param enums List of Enum objects
99"""
100class OFProtocol(namedtuple('OFProtocol', ['version', 'classes', 'enums'])):
101 def __init__(self, version, classes, enums):
102 super(OFProtocol, self).__init__(self, version, classes, enums)
103 assert version is None or isinstance(version, OFVersion)
104
105 def class_by_name(self, name):
106 return find(lambda ofclass: ofclass.name == name, self.classes)
107
108 def enum_by_name(self, name):
109 return find(lambda enum: enum.name == name, self.enums)
110
111"""
112An OpenFlow class
113
114All compound objects like messages, actions, instructions, etc are
115uniformly represented by this class.
116
117The members are in the same order as on the wire.
118
119@param name
120@param superclass_name of this classes' super class
121@param members List of *Member objects
122@param params optional dictionary of parameters
123"""
124class OFClass(namedtuple('OFClass', ['name', 'superclass', 'members', 'virtual', 'params', 'is_fixed_length', 'base_length'])):
125 def __init__(self, *a, **kw):
126 super(OFClass, self).__init__(self, *a, **kw)
127 # Back reference will be added by assignment
128 self.protocol = None
129
130 def member_by_name(self, name):
131 return find(lambda m: hasattr(m, "name") and m.name == name, self.members)
132
133 @property
134 def discriminator(self):
135 return find(lambda m: type(m) == OFDiscriminatorMember, self.members)
136
137 def is_instanceof(self, super_class_name):
138 if self.name == super_class_name:
139 return True
140 elif self.superclass is None:
141 return False
142 else:
143 return self.superclass.is_instanceof(super_class_name)
144
145 def is_subclassof(self, super_class_name):
146 return self.name != super_class_name and self.is_instanceof(super_class_name)
147
148 @property
149 def is_message(self):
150 return self.is_instanceof("of_header")
151
152 @property
153 def is_oxm(self):
154 return self.is_instanceof("of_oxm")
155
156 @property
157 def is_action(self):
158 return self.is_instanceof("of_action")
159
160 @property
161 def is_action_id(self):
162 return self.is_instanceof("of_action_id")
163
164 @property
165 def is_instruction(self):
166 return self.is_instanceof("of_instruction")
167
168 def __hash__(self):
169 return hash((self.name, self.protocol.wire_version if self.protocol else None))
170
171 @property
172 def length(self):
173 if self.is_fixed_length:
174 return self.base_length
175 else:
Andreas Wundsam55ecc3c2013-11-15 16:11:52 -0800176 raise Exception("Not a fixed length class: {}".format(self.name))
Andreas Wundsamd30c1072013-11-15 13:36:57 -0800177
Rich Lane25e73f02013-12-02 14:44:32 -0800178 @property
179 def has_internal_alignment(self):
180 return self.params.get('length_includes_align') == 'True'
181
182 @property
183 def has_external_alignment(self):
184 return self.params.get('length_includes_align') == 'False'
185
Rich Lane5fe77212013-12-10 10:56:47 -0800186 @property
187 def has_type_members(self):
188 return find(lambda m: isinstance(m, OFTypeMember), self.members) is not None
189
Andreas Wundsamd30c1072013-11-15 13:36:57 -0800190""" one class unified across openflow versions. Keeps around a map version->versioned_class """
191class OFUnifiedClass(OFClass):
192 def __new__(cls, version_classes, *a, **kw):
193 return super(OFUnifiedClass, cls).__new__(cls, *a, **kw)
194
195 def __init__(self, version_classes, *a, **kw):
196 super(OFUnifiedClass, self).__init__(*a, **kw)
197 self.version_classes = version_classes
198
Andreas Wundsam343abd92013-11-16 13:16:07 -0800199 def class_by_version(self, version):
Andreas Wundsamd30c1072013-11-15 13:36:57 -0800200 return self.version_classes[version]
201
202
203
204""" A mixin for member classes. Keeps around the back reference of_class (for assignment by
205 build_protocol, and additional methods shared across Members. """
206class MemberMixin(object):
207 def __init__(self, *a, **kw):
208 super(MemberMixin, self).__init__(*a, **kw)
209 # Back reference will be added by assignment in build_protocol below
210 self.of_class = None
211
212 @property
213 def length(self):
214 if self.is_fixed_length:
215 return self.base_length
216 else:
217 raise Exception("Not a fixed length member: {}.{} [{}]".format(
218 self.of_class.name,
219 self.name if hasattr("self", name) else "(unnnamed)",
220 type(self).__name__))
221
222"""
223Normal field
224
225@param name
226@param oftype C-like type string
227
228Example: packet_in.buffer_id
229"""
230class OFDataMember(namedtuple('OFDataMember', ['name', 'oftype', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
231 pass
232
233"""
234Field that declares that this is an abstract super-class and
235that the sub classes will be discriminated based on this field.
236E.g., 'type' is the discriminator member of the abstract superclass
237of_action.
238
239@param name
240"""
241class OFDiscriminatorMember (namedtuple('OFDiscriminatorMember', ['name', 'oftype', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
242 pass
243
244"""
245Field used to determine the type of an OpenFlow object
246
247@param name
248@param oftype C-like type string
249@param value Fixed type value
250
251Example: packet_in.type, flow_add._command
252"""
253class OFTypeMember (namedtuple('OFTypeMember', ['name', 'oftype', 'value', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
254 pass
255
256"""
257Field with the length of the containing object
258
259@param name
260@param oftype C-like type string
261
262Example: packet_in.length, action_output.len
263"""
264class OFLengthMember (namedtuple('OFLengthMember', ['name', 'oftype', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
265 pass
266
267"""
268Field with the length of another field in the containing object
269
270@param name
271@param oftype C-like type string
272@param field_name Peer field whose length this field contains
273
274Example: packet_out.actions_len (only usage)
275"""
276class OFFieldLengthMember (namedtuple('OFFieldLengthMember', ['name', 'oftype', 'field_name', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
277 pass
278
279"""
280Zero-filled padding
281
282@param length Length in bytes
283
284Example: packet_in.pad
285"""
286class OFPadMember (namedtuple('OFPadMember', ['pad_length', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
287 pass
288
289"""
290An OpenFlow enumeration
291
292All values are Python ints.
293
294@param name
295@param entries List of OFEnumEntry objects in input order
296@params dict of optional params. Currently defined:
297 - wire_type: the low_level type of the enum values (uint8,...)
298"""
299class OFEnum(namedtuple('OFEnum', ['name', 'entries', 'params'])):
300 def __init__(self, *a, **kw):
301 super(OFEnum, self).__init__(*a, **kw)
302 # Back reference will be added by assignment
303 self.protocol = None
304
305 @property
306 def values(self):
307 return [(e.name, e.value) for e in self.entries]
308
309 @property
310 def is_bitmask(self):
311 return "bitmask" in self.params and self.params['bitmask']
312
313 @property
314 def wire_type(self):
315 return self.params['wire_type'] if 'wire_type' in self.params else self.name
316
317class OFEnumEntry(namedtuple('OFEnumEntry', ['name', 'value', 'params'])):
318 def __init__(self, *a, **kw):
319 super(OFEnumEntry, self).__init__(*a, **kw)
320 # Back reference will be added by assignment
321 self.enum = None
322
323class RedefinedException(Exception):
324 pass
325
326class ClassNotFoundException(Exception):
327 pass
328
329class DependencyCycleException(Exception):
330 pass
331
332def build_protocol(version, ofinputs):
333 name_frontend_classes = OrderedDict()
334 name_frontend_enums = OrderedDict()
335
336 for ofinput in ofinputs:
337 for c in ofinput.classes:
338 name = c.name
339 if name in name_frontend_classes:
340 raise RedefinedException("Error parsing {}. Class {} redefined (already defined in {})"
341 .format(ofinput.filename, name,
342 name_frontend_classes[name][1].filename))
343 else:
344 name_frontend_classes[name] = (c, ofinput)
345 for e in ofinput.enums:
346 name = e.name
347 if name in name_frontend_enums:
348 raise RedefinedException("Error parsing {}. Enum {} redefined (already defined in {})"
349 .format(ofinput.filename, name,
350 name_frontend_enums[name][1].filename))
351 else:
352 name_frontend_enums[name] = (e, ofinput)
353
354 name_enums = {}
355 for fe, _ in name_frontend_enums.values():
356 entries = tuple(OFEnumEntry(name=e.name, value=e.value,
357 params=e.params) for e in fe.entries)
358 enum = OFEnum(name=fe.name,
359 entries=entries,
360 params=fe.params)
361 for e in entries:
362 e.enum = enum
363 name_enums[enum.name] = enum
364
365 name_classes = OrderedDict()
366 build_touch_classes = OrderedSet()
367
368 def convert_member_properties(props):
369 return { name if name != "length" else "pad_length" : value for name, value in props.items() }
370
371 def build_member(of_class, fe_member, length_info):
Rich Lane7bc23772013-11-29 17:47:46 -0800372 if isinstance(fe_member, frontend_ir.OFVersionMember):
373 member = OFTypeMember(offset = length_info.offset,
374 base_length = length_info.base_length,
375 is_fixed_length=length_info.is_fixed_length,
376 value = version.wire_version,
377 **convert_member_properties(fe_member._asdict()))
378 else:
379 ir_class = globals()[type(fe_member).__name__]
380 member = ir_class(offset = length_info.offset,
381 base_length = length_info.base_length,
382 is_fixed_length=length_info.is_fixed_length,
383 **convert_member_properties(fe_member._asdict()))
Andreas Wundsamd30c1072013-11-15 13:36:57 -0800384 member.of_class = of_class
385 return member
386
387 def build_class(name):
388 if name in name_classes:
389 return name_classes[name]
390 if name in build_touch_classes:
391 raise DependencyCycleException( "Dependency cycle: {}"
392 .format(" -> ".join(list(build_touch_classes) + [name])))
393 if not name in name_frontend_classes:
394 raise ClassNotFoundException("Class not found: {}".format(name))
395
396 build_touch_classes.add(name)
397
398 fe, _ = name_frontend_classes[name]
399
400 superclass = build_class(fe.superclass) if fe.superclass else None
401
402 # make sure members on which we depend are built first (for calc_length)
403 for m in fe.members:
404 if not hasattr(m, "oftype"):
405 continue
406 for m_name in re.sub(r'_t$', '', m.oftype), m.oftype:
407 logger.debug("Checking {}".format(m_name))
408 if m_name in name_frontend_classes:
409 build_class(m_name)
410
411 base_length, is_fixed_length, member_lengths = \
412 ir_offset.calc_lengths(version, fe, name_classes, name_enums)
413
414 members = []
415 c = OFClass(name=fe.name, superclass=superclass,
416 members=members, virtual=fe.virtual, params=fe.params,
417 is_fixed_length=is_fixed_length, base_length=base_length)
418
419 members.extend( build_member(c, fe_member, member_lengths[fe_member])
420 for fe_member in fe.members)
421
422 name_classes[name] = c
423 build_touch_classes.remove(name)
424 return c
425
Rich Laneb87348d2013-12-09 17:18:51 -0800426 def build_id_class(orig_name, base_name):
427 name = base_name + '_id' + orig_name[len(base_name):]
428 if name in name_classes:
429 return name_classes[name]
430 orig_fe, _ = name_frontend_classes[orig_name]
431
432 if orig_fe.superclass:
433 superclass_name = base_name + '_id' + orig_fe.superclass[len(base_name):]
434 superclass = build_id_class(orig_fe.superclass, base_name)
435 else:
436 superclass_name = None
437 superclass = None
438
439 fe = frontend_ir.OFClass(
440 name=name,
441 superclass=superclass_name,
442 members=[m for m in orig_fe.members if not isinstance(m, frontend_ir.OFDataMember)],
443 virtual=orig_fe.virtual,
444 params={})
445
446 base_length, is_fixed_length, member_lengths = \
447 ir_offset.calc_lengths(version, fe, name_classes, name_enums)
448 assert fe.virtual or is_fixed_length
449
450 members = []
451 c = OFClass(name=fe.name, superclass=superclass,
452 members=members, virtual=fe.virtual, params=fe.params,
453 is_fixed_length=is_fixed_length, base_length=base_length)
454
455 members.extend( build_member(c, fe_member, member_lengths[fe_member])
456 for fe_member in fe.members)
457
458 name_classes[name] = c
459 return c
460
461 id_class_roots = ["of_action", "of_instruction"]
462
Andreas Wundsamd30c1072013-11-15 13:36:57 -0800463 for name in sorted(name_frontend_classes.keys()):
464 c = build_class(name)
465
Rich Laneb87348d2013-12-09 17:18:51 -0800466 # Build ID classes for OF 1.3+
467 if version.wire_version >= 4:
468 for root in id_class_roots:
469 if c.is_instanceof(root):
470 build_id_class(name, root)
471
Andreas Wundsamd30c1072013-11-15 13:36:57 -0800472 protocol = OFProtocol(version=version, classes=tuple(name_classes.values()), enums=tuple(name_enums.values()))
473 for e in chain(protocol.classes, protocol.enums):
474 e.protocol = protocol
475 return protocol