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