blob: df1c77d53a2b584c539a1717b8dab32a307388d7 [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
Andreas Wundsamd30c1072013-11-15 13:36:57 -0800186""" one class unified across openflow versions. Keeps around a map version->versioned_class """
187class OFUnifiedClass(OFClass):
188 def __new__(cls, version_classes, *a, **kw):
189 return super(OFUnifiedClass, cls).__new__(cls, *a, **kw)
190
191 def __init__(self, version_classes, *a, **kw):
192 super(OFUnifiedClass, self).__init__(*a, **kw)
193 self.version_classes = version_classes
194
Andreas Wundsam343abd92013-11-16 13:16:07 -0800195 def class_by_version(self, version):
Andreas Wundsamd30c1072013-11-15 13:36:57 -0800196 return self.version_classes[version]
197
198
199
200""" A mixin for member classes. Keeps around the back reference of_class (for assignment by
201 build_protocol, and additional methods shared across Members. """
202class MemberMixin(object):
203 def __init__(self, *a, **kw):
204 super(MemberMixin, self).__init__(*a, **kw)
205 # Back reference will be added by assignment in build_protocol below
206 self.of_class = None
207
208 @property
209 def length(self):
210 if self.is_fixed_length:
211 return self.base_length
212 else:
213 raise Exception("Not a fixed length member: {}.{} [{}]".format(
214 self.of_class.name,
215 self.name if hasattr("self", name) else "(unnnamed)",
216 type(self).__name__))
217
218"""
219Normal field
220
221@param name
222@param oftype C-like type string
223
224Example: packet_in.buffer_id
225"""
226class OFDataMember(namedtuple('OFDataMember', ['name', 'oftype', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
227 pass
228
229"""
230Field that declares that this is an abstract super-class and
231that the sub classes will be discriminated based on this field.
232E.g., 'type' is the discriminator member of the abstract superclass
233of_action.
234
235@param name
236"""
237class OFDiscriminatorMember (namedtuple('OFDiscriminatorMember', ['name', 'oftype', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
238 pass
239
240"""
241Field used to determine the type of an OpenFlow object
242
243@param name
244@param oftype C-like type string
245@param value Fixed type value
246
247Example: packet_in.type, flow_add._command
248"""
249class OFTypeMember (namedtuple('OFTypeMember', ['name', 'oftype', 'value', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
250 pass
251
252"""
253Field with the length of the containing object
254
255@param name
256@param oftype C-like type string
257
258Example: packet_in.length, action_output.len
259"""
260class OFLengthMember (namedtuple('OFLengthMember', ['name', 'oftype', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
261 pass
262
263"""
264Field with the length of another field in the containing object
265
266@param name
267@param oftype C-like type string
268@param field_name Peer field whose length this field contains
269
270Example: packet_out.actions_len (only usage)
271"""
272class OFFieldLengthMember (namedtuple('OFFieldLengthMember', ['name', 'oftype', 'field_name', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
273 pass
274
275"""
276Zero-filled padding
277
278@param length Length in bytes
279
280Example: packet_in.pad
281"""
282class OFPadMember (namedtuple('OFPadMember', ['pad_length', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
283 pass
284
285"""
286An OpenFlow enumeration
287
288All values are Python ints.
289
290@param name
291@param entries List of OFEnumEntry objects in input order
292@params dict of optional params. Currently defined:
293 - wire_type: the low_level type of the enum values (uint8,...)
294"""
295class OFEnum(namedtuple('OFEnum', ['name', 'entries', 'params'])):
296 def __init__(self, *a, **kw):
297 super(OFEnum, self).__init__(*a, **kw)
298 # Back reference will be added by assignment
299 self.protocol = None
300
301 @property
302 def values(self):
303 return [(e.name, e.value) for e in self.entries]
304
305 @property
306 def is_bitmask(self):
307 return "bitmask" in self.params and self.params['bitmask']
308
309 @property
310 def wire_type(self):
311 return self.params['wire_type'] if 'wire_type' in self.params else self.name
312
313class OFEnumEntry(namedtuple('OFEnumEntry', ['name', 'value', 'params'])):
314 def __init__(self, *a, **kw):
315 super(OFEnumEntry, self).__init__(*a, **kw)
316 # Back reference will be added by assignment
317 self.enum = None
318
319class RedefinedException(Exception):
320 pass
321
322class ClassNotFoundException(Exception):
323 pass
324
325class DependencyCycleException(Exception):
326 pass
327
328def build_protocol(version, ofinputs):
329 name_frontend_classes = OrderedDict()
330 name_frontend_enums = OrderedDict()
331
332 for ofinput in ofinputs:
333 for c in ofinput.classes:
334 name = c.name
335 if name in name_frontend_classes:
336 raise RedefinedException("Error parsing {}. Class {} redefined (already defined in {})"
337 .format(ofinput.filename, name,
338 name_frontend_classes[name][1].filename))
339 else:
340 name_frontend_classes[name] = (c, ofinput)
341 for e in ofinput.enums:
342 name = e.name
343 if name in name_frontend_enums:
344 raise RedefinedException("Error parsing {}. Enum {} redefined (already defined in {})"
345 .format(ofinput.filename, name,
346 name_frontend_enums[name][1].filename))
347 else:
348 name_frontend_enums[name] = (e, ofinput)
349
350 name_enums = {}
351 for fe, _ in name_frontend_enums.values():
352 entries = tuple(OFEnumEntry(name=e.name, value=e.value,
353 params=e.params) for e in fe.entries)
354 enum = OFEnum(name=fe.name,
355 entries=entries,
356 params=fe.params)
357 for e in entries:
358 e.enum = enum
359 name_enums[enum.name] = enum
360
361 name_classes = OrderedDict()
362 build_touch_classes = OrderedSet()
363
364 def convert_member_properties(props):
365 return { name if name != "length" else "pad_length" : value for name, value in props.items() }
366
367 def build_member(of_class, fe_member, length_info):
Rich Lane7bc23772013-11-29 17:47:46 -0800368 if isinstance(fe_member, frontend_ir.OFVersionMember):
369 member = OFTypeMember(offset = length_info.offset,
370 base_length = length_info.base_length,
371 is_fixed_length=length_info.is_fixed_length,
372 value = version.wire_version,
373 **convert_member_properties(fe_member._asdict()))
374 else:
375 ir_class = globals()[type(fe_member).__name__]
376 member = ir_class(offset = length_info.offset,
377 base_length = length_info.base_length,
378 is_fixed_length=length_info.is_fixed_length,
379 **convert_member_properties(fe_member._asdict()))
Andreas Wundsamd30c1072013-11-15 13:36:57 -0800380 member.of_class = of_class
381 return member
382
383 def build_class(name):
384 if name in name_classes:
385 return name_classes[name]
386 if name in build_touch_classes:
387 raise DependencyCycleException( "Dependency cycle: {}"
388 .format(" -> ".join(list(build_touch_classes) + [name])))
389 if not name in name_frontend_classes:
390 raise ClassNotFoundException("Class not found: {}".format(name))
391
392 build_touch_classes.add(name)
393
394 fe, _ = name_frontend_classes[name]
395
396 superclass = build_class(fe.superclass) if fe.superclass else None
397
398 # make sure members on which we depend are built first (for calc_length)
399 for m in fe.members:
400 if not hasattr(m, "oftype"):
401 continue
402 for m_name in re.sub(r'_t$', '', m.oftype), m.oftype:
403 logger.debug("Checking {}".format(m_name))
404 if m_name in name_frontend_classes:
405 build_class(m_name)
406
407 base_length, is_fixed_length, member_lengths = \
408 ir_offset.calc_lengths(version, fe, name_classes, name_enums)
409
410 members = []
411 c = OFClass(name=fe.name, superclass=superclass,
412 members=members, virtual=fe.virtual, params=fe.params,
413 is_fixed_length=is_fixed_length, base_length=base_length)
414
415 members.extend( build_member(c, fe_member, member_lengths[fe_member])
416 for fe_member in fe.members)
417
418 name_classes[name] = c
419 build_touch_classes.remove(name)
420 return c
421
422 for name in sorted(name_frontend_classes.keys()):
423 c = build_class(name)
424
425 protocol = OFProtocol(version=version, classes=tuple(name_classes.values()), enums=tuple(name_enums.values()))
426 for e in chain(protocol.classes, protocol.enums):
427 e.protocol = protocol
428 return protocol