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