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