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