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