blob: 6a053c61baf98ae80f7f061e5837e2c954bc8eed [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
Tomasz6f3ba6b2013-12-16 21:20:49 -0800179 def length_member(self):
180 return find(lambda m: type(m) == OFLengthMember, self.members)
Tomaszf705e0a2013-12-14 21:49:42 -0800181
182 @property
Rich Lane25e73f02013-12-02 14:44:32 -0800183 def has_internal_alignment(self):
184 return self.params.get('length_includes_align') == 'True'
185
186 @property
187 def has_external_alignment(self):
188 return self.params.get('length_includes_align') == 'False'
189
Rich Lane5fe77212013-12-10 10:56:47 -0800190 @property
191 def has_type_members(self):
192 return find(lambda m: isinstance(m, OFTypeMember), self.members) is not None
193
Andreas Wundsamd30c1072013-11-15 13:36:57 -0800194""" one class unified across openflow versions. Keeps around a map version->versioned_class """
195class OFUnifiedClass(OFClass):
196 def __new__(cls, version_classes, *a, **kw):
197 return super(OFUnifiedClass, cls).__new__(cls, *a, **kw)
198
199 def __init__(self, version_classes, *a, **kw):
200 super(OFUnifiedClass, self).__init__(*a, **kw)
201 self.version_classes = version_classes
202
Andreas Wundsam343abd92013-11-16 13:16:07 -0800203 def class_by_version(self, version):
Andreas Wundsamd30c1072013-11-15 13:36:57 -0800204 return self.version_classes[version]
205
206
207
208""" A mixin for member classes. Keeps around the back reference of_class (for assignment by
209 build_protocol, and additional methods shared across Members. """
210class MemberMixin(object):
211 def __init__(self, *a, **kw):
212 super(MemberMixin, self).__init__(*a, **kw)
213 # Back reference will be added by assignment in build_protocol below
214 self.of_class = None
215
216 @property
217 def length(self):
218 if self.is_fixed_length:
219 return self.base_length
220 else:
221 raise Exception("Not a fixed length member: {}.{} [{}]".format(
222 self.of_class.name,
223 self.name if hasattr("self", name) else "(unnnamed)",
224 type(self).__name__))
225
226"""
227Normal field
228
229@param name
230@param oftype C-like type string
231
232Example: packet_in.buffer_id
233"""
234class OFDataMember(namedtuple('OFDataMember', ['name', 'oftype', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
235 pass
236
237"""
238Field that declares that this is an abstract super-class and
239that the sub classes will be discriminated based on this field.
240E.g., 'type' is the discriminator member of the abstract superclass
241of_action.
242
243@param name
244"""
245class OFDiscriminatorMember (namedtuple('OFDiscriminatorMember', ['name', 'oftype', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
246 pass
247
248"""
249Field used to determine the type of an OpenFlow object
250
251@param name
252@param oftype C-like type string
253@param value Fixed type value
254
255Example: packet_in.type, flow_add._command
256"""
257class OFTypeMember (namedtuple('OFTypeMember', ['name', 'oftype', 'value', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
258 pass
259
260"""
261Field with the length of the containing object
262
263@param name
264@param oftype C-like type string
265
266Example: packet_in.length, action_output.len
267"""
268class OFLengthMember (namedtuple('OFLengthMember', ['name', 'oftype', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
269 pass
270
271"""
272Field with the length of another field in the containing object
273
274@param name
275@param oftype C-like type string
276@param field_name Peer field whose length this field contains
277
278Example: packet_out.actions_len (only usage)
279"""
280class OFFieldLengthMember (namedtuple('OFFieldLengthMember', ['name', 'oftype', 'field_name', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
281 pass
282
283"""
284Zero-filled padding
285
286@param length Length in bytes
287
288Example: packet_in.pad
289"""
290class OFPadMember (namedtuple('OFPadMember', ['pad_length', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
291 pass
292
293"""
294An OpenFlow enumeration
295
296All values are Python ints.
297
298@param name
299@param entries List of OFEnumEntry objects in input order
300@params dict of optional params. Currently defined:
301 - wire_type: the low_level type of the enum values (uint8,...)
302"""
303class OFEnum(namedtuple('OFEnum', ['name', 'entries', 'params'])):
304 def __init__(self, *a, **kw):
305 super(OFEnum, self).__init__(*a, **kw)
306 # Back reference will be added by assignment
307 self.protocol = None
308
309 @property
310 def values(self):
311 return [(e.name, e.value) for e in self.entries]
312
313 @property
314 def is_bitmask(self):
315 return "bitmask" in self.params and self.params['bitmask']
316
317 @property
318 def wire_type(self):
319 return self.params['wire_type'] if 'wire_type' in self.params else self.name
320
321class OFEnumEntry(namedtuple('OFEnumEntry', ['name', 'value', 'params'])):
322 def __init__(self, *a, **kw):
323 super(OFEnumEntry, self).__init__(*a, **kw)
324 # Back reference will be added by assignment
325 self.enum = None
326
327class RedefinedException(Exception):
328 pass
329
330class ClassNotFoundException(Exception):
331 pass
332
333class DependencyCycleException(Exception):
334 pass
335
336def build_protocol(version, ofinputs):
337 name_frontend_classes = OrderedDict()
338 name_frontend_enums = OrderedDict()
339
340 for ofinput in ofinputs:
341 for c in ofinput.classes:
342 name = c.name
343 if name in name_frontend_classes:
344 raise RedefinedException("Error parsing {}. Class {} redefined (already defined in {})"
345 .format(ofinput.filename, name,
346 name_frontend_classes[name][1].filename))
347 else:
348 name_frontend_classes[name] = (c, ofinput)
349 for e in ofinput.enums:
350 name = e.name
351 if name in name_frontend_enums:
352 raise RedefinedException("Error parsing {}. Enum {} redefined (already defined in {})"
353 .format(ofinput.filename, name,
354 name_frontend_enums[name][1].filename))
355 else:
356 name_frontend_enums[name] = (e, ofinput)
357
358 name_enums = {}
359 for fe, _ in name_frontend_enums.values():
360 entries = tuple(OFEnumEntry(name=e.name, value=e.value,
361 params=e.params) for e in fe.entries)
362 enum = OFEnum(name=fe.name,
363 entries=entries,
364 params=fe.params)
365 for e in entries:
366 e.enum = enum
367 name_enums[enum.name] = enum
368
369 name_classes = OrderedDict()
370 build_touch_classes = OrderedSet()
371
372 def convert_member_properties(props):
373 return { name if name != "length" else "pad_length" : value for name, value in props.items() }
374
375 def build_member(of_class, fe_member, length_info):
Rich Lane7bc23772013-11-29 17:47:46 -0800376 if isinstance(fe_member, frontend_ir.OFVersionMember):
377 member = OFTypeMember(offset = length_info.offset,
378 base_length = length_info.base_length,
379 is_fixed_length=length_info.is_fixed_length,
380 value = version.wire_version,
381 **convert_member_properties(fe_member._asdict()))
382 else:
383 ir_class = globals()[type(fe_member).__name__]
384 member = ir_class(offset = length_info.offset,
385 base_length = length_info.base_length,
386 is_fixed_length=length_info.is_fixed_length,
387 **convert_member_properties(fe_member._asdict()))
Andreas Wundsamd30c1072013-11-15 13:36:57 -0800388 member.of_class = of_class
389 return member
390
391 def build_class(name):
392 if name in name_classes:
393 return name_classes[name]
394 if name in build_touch_classes:
395 raise DependencyCycleException( "Dependency cycle: {}"
396 .format(" -> ".join(list(build_touch_classes) + [name])))
397 if not name in name_frontend_classes:
398 raise ClassNotFoundException("Class not found: {}".format(name))
399
400 build_touch_classes.add(name)
401
402 fe, _ = name_frontend_classes[name]
403
404 superclass = build_class(fe.superclass) if fe.superclass else None
405
406 # make sure members on which we depend are built first (for calc_length)
407 for m in fe.members:
408 if not hasattr(m, "oftype"):
409 continue
410 for m_name in re.sub(r'_t$', '', m.oftype), m.oftype:
411 logger.debug("Checking {}".format(m_name))
412 if m_name in name_frontend_classes:
413 build_class(m_name)
414
415 base_length, is_fixed_length, member_lengths = \
416 ir_offset.calc_lengths(version, fe, name_classes, name_enums)
417
418 members = []
419 c = OFClass(name=fe.name, superclass=superclass,
420 members=members, virtual=fe.virtual, params=fe.params,
421 is_fixed_length=is_fixed_length, base_length=base_length)
422
423 members.extend( build_member(c, fe_member, member_lengths[fe_member])
424 for fe_member in fe.members)
425
426 name_classes[name] = c
427 build_touch_classes.remove(name)
428 return c
429
Rich Laneb87348d2013-12-09 17:18:51 -0800430 def build_id_class(orig_name, base_name):
431 name = base_name + '_id' + orig_name[len(base_name):]
432 if name in name_classes:
433 return name_classes[name]
434 orig_fe, _ = name_frontend_classes[orig_name]
435
436 if orig_fe.superclass:
437 superclass_name = base_name + '_id' + orig_fe.superclass[len(base_name):]
438 superclass = build_id_class(orig_fe.superclass, base_name)
439 else:
440 superclass_name = None
441 superclass = None
442
443 fe = frontend_ir.OFClass(
444 name=name,
445 superclass=superclass_name,
446 members=[m for m in orig_fe.members if not isinstance(m, frontend_ir.OFDataMember)],
447 virtual=orig_fe.virtual,
448 params={})
449
450 base_length, is_fixed_length, member_lengths = \
451 ir_offset.calc_lengths(version, fe, name_classes, name_enums)
452 assert fe.virtual or is_fixed_length
453
454 members = []
455 c = OFClass(name=fe.name, superclass=superclass,
456 members=members, virtual=fe.virtual, params=fe.params,
457 is_fixed_length=is_fixed_length, base_length=base_length)
458
459 members.extend( build_member(c, fe_member, member_lengths[fe_member])
460 for fe_member in fe.members)
461
462 name_classes[name] = c
463 return c
464
465 id_class_roots = ["of_action", "of_instruction"]
466
Andreas Wundsamd30c1072013-11-15 13:36:57 -0800467 for name in sorted(name_frontend_classes.keys()):
468 c = build_class(name)
469
Rich Laneb87348d2013-12-09 17:18:51 -0800470 # Build ID classes for OF 1.3+
471 if version.wire_version >= 4:
472 for root in id_class_roots:
473 if c.is_instanceof(root):
474 build_id_class(name, root)
475
Andreas Wundsamd30c1072013-11-15 13:36:57 -0800476 protocol = OFProtocol(version=version, classes=tuple(name_classes.values()), enums=tuple(name_enums.values()))
477 for e in chain(protocol.classes, protocol.enums):
478 e.protocol = protocol
479 return protocol