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