loxi_ir: Moved to its own package
to group ir and related functionality. Also introduce a dedicated
frontend_ir model, and adds a unit test
diff --git a/loxi_ir/ir.py b/loxi_ir/ir.py
new file mode 100644
index 0000000..21f760c
--- /dev/null
+++ b/loxi_ir/ir.py
@@ -0,0 +1,412 @@
+# Copyright 2013, Big Switch Networks, Inc.
+#
+# LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
+# the following special exception:
+#
+# LOXI Exception
+#
+# As a special exception to the terms of the EPL, you may distribute libraries
+# generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
+# that copyright and licensing notices generated by LoxiGen are not altered or removed
+# from the LoxiGen Libraries and the notice provided below is (i) included in
+# the LoxiGen Libraries, if distributed in source code form and (ii) included in any
+# documentation for the LoxiGen Libraries, if distributed in binary form.
+#
+# Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
+#
+# You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
+# a copy of the EPL at:
+#
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# EPL for the specific language governing permissions and limitations
+# under the EPL.
+
+from itertools import chain
+import logging
+import re
+import sys
+
+from collections import namedtuple, OrderedDict
+from generic_utils import find, memoize, OrderedSet
+from loxi_ir import ir_offset
+
+logger = logging.getLogger(__name__)
+
+# This module is intended to be imported like this: from loxi_ir import *
+# All public names are prefixed with 'OF'.
+__all__ = [
+ 'OFVersion',
+ 'OFProtocol',
+ 'OFClass',
+ 'OFUnifiedClass',
+ 'OFDataMember',
+ 'OFTypeMember',
+ 'OFDiscriminatorMember',
+ 'OFLengthMember',
+ 'OFFieldLengthMember',
+ 'OFPadMember',
+ 'OFEnum',
+ 'OFEnumEntry'
+]
+
+"""
+One version of the OpenFlow protocol
+@param version Official dotted version number (e.g., "1.0", "1.3")
+@param wire_version Integer wire version (1 for 1.0, 4 for 1.3)
+"""
+class OFVersion(namedtuple("OFVersion", ("version", "wire_version"))):
+ @property
+ @memoize
+ def constant(self):
+ """ return this version as an uppercase string suitable
+ for use as a c constant, e.g., "VERSION_1_3"
+ """
+ return self.constant_version(prefix="VERSION_")
+
+ @property
+ @memoize
+ def short_constant(self):
+ """ return this version as an uppercase string suitable
+ for use as a c constant, e.g., "OF_"
+ """
+ return self.constant_version(prefix="OF_")
+
+ def constant_version(self, prefix="VERSION_"):
+ return prefix + self.version.replace(".", "_")
+
+ def __repr__(self):
+ return "OFVersion(%s)" % self.version
+
+ def __str__(self):
+ return self.version
+
+ def __cmp__(self, other):
+ return cmp(self.wire_version, other.wire_version)
+
+"""
+One version of the OpenFlow protocol
+
+Combination of multiple OFInput objects.
+
+@param wire_version
+@param classes List of OFClass objects
+@param enums List of Enum objects
+"""
+class OFProtocol(namedtuple('OFProtocol', ['version', 'classes', 'enums'])):
+ def __init__(self, version, classes, enums):
+ super(OFProtocol, self).__init__(self, version, classes, enums)
+ assert version is None or isinstance(version, OFVersion)
+
+ def class_by_name(self, name):
+ return find(lambda ofclass: ofclass.name == name, self.classes)
+
+ def enum_by_name(self, name):
+ return find(lambda enum: enum.name == name, self.enums)
+
+"""
+An OpenFlow class
+
+All compound objects like messages, actions, instructions, etc are
+uniformly represented by this class.
+
+The members are in the same order as on the wire.
+
+@param name
+@param superclass_name of this classes' super class
+@param members List of *Member objects
+@param params optional dictionary of parameters
+"""
+class OFClass(namedtuple('OFClass', ['name', 'superclass', 'members', 'virtual', 'params', 'is_fixed_length', 'base_length'])):
+ def __init__(self, *a, **kw):
+ super(OFClass, self).__init__(self, *a, **kw)
+ # Back reference will be added by assignment
+ self.protocol = None
+
+ def member_by_name(self, name):
+ return find(lambda m: hasattr(m, "name") and m.name == name, self.members)
+
+ @property
+ def discriminator(self):
+ return find(lambda m: type(m) == OFDiscriminatorMember, self.members)
+
+ def is_instanceof(self, super_class_name):
+ if self.name == super_class_name:
+ return True
+ elif self.superclass is None:
+ return False
+ else:
+ return self.superclass.is_instanceof(super_class_name)
+
+ def is_subclassof(self, super_class_name):
+ return self.name != super_class_name and self.is_instanceof(super_class_name)
+
+ @property
+ def is_message(self):
+ return self.is_instanceof("of_header")
+
+ @property
+ def is_oxm(self):
+ return self.is_instanceof("of_oxm")
+
+ @property
+ def is_action(self):
+ return self.is_instanceof("of_action")
+
+ @property
+ def is_action_id(self):
+ return self.is_instanceof("of_action_id")
+
+ @property
+ def is_instruction(self):
+ return self.is_instanceof("of_instruction")
+
+ def __hash__(self):
+ return hash((self.name, self.protocol.wire_version if self.protocol else None))
+
+ @property
+ def length(self):
+ if self.is_fixed_length:
+ return self.base_length
+ else:
+ raise Exception("Not a fixed length class: {}".self.name)
+
+""" one class unified across openflow versions. Keeps around a map version->versioned_class """
+class OFUnifiedClass(OFClass):
+ def __new__(cls, version_classes, *a, **kw):
+ return super(OFUnifiedClass, cls).__new__(cls, *a, **kw)
+
+ def __init__(self, version_classes, *a, **kw):
+ super(OFUnifiedClass, self).__init__(*a, **kw)
+ self.version_classes = version_classes
+
+ def class_by_version(version):
+ return self.version_classes[version]
+
+
+
+""" A mixin for member classes. Keeps around the back reference of_class (for assignment by
+ build_protocol, and additional methods shared across Members. """
+class MemberMixin(object):
+ def __init__(self, *a, **kw):
+ super(MemberMixin, self).__init__(*a, **kw)
+ # Back reference will be added by assignment in build_protocol below
+ self.of_class = None
+
+ @property
+ def length(self):
+ if self.is_fixed_length:
+ return self.base_length
+ else:
+ raise Exception("Not a fixed length member: {}.{} [{}]".format(
+ self.of_class.name,
+ self.name if hasattr("self", name) else "(unnnamed)",
+ type(self).__name__))
+
+"""
+Normal field
+
+@param name
+@param oftype C-like type string
+
+Example: packet_in.buffer_id
+"""
+class OFDataMember(namedtuple('OFDataMember', ['name', 'oftype', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
+ pass
+
+"""
+Field that declares that this is an abstract super-class and
+that the sub classes will be discriminated based on this field.
+E.g., 'type' is the discriminator member of the abstract superclass
+of_action.
+
+@param name
+"""
+class OFDiscriminatorMember (namedtuple('OFDiscriminatorMember', ['name', 'oftype', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
+ pass
+
+"""
+Field used to determine the type of an OpenFlow object
+
+@param name
+@param oftype C-like type string
+@param value Fixed type value
+
+Example: packet_in.type, flow_add._command
+"""
+class OFTypeMember (namedtuple('OFTypeMember', ['name', 'oftype', 'value', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
+ pass
+
+"""
+Field with the length of the containing object
+
+@param name
+@param oftype C-like type string
+
+Example: packet_in.length, action_output.len
+"""
+class OFLengthMember (namedtuple('OFLengthMember', ['name', 'oftype', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
+ pass
+
+"""
+Field with the length of another field in the containing object
+
+@param name
+@param oftype C-like type string
+@param field_name Peer field whose length this field contains
+
+Example: packet_out.actions_len (only usage)
+"""
+class OFFieldLengthMember (namedtuple('OFFieldLengthMember', ['name', 'oftype', 'field_name', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
+ pass
+
+"""
+Zero-filled padding
+
+@param length Length in bytes
+
+Example: packet_in.pad
+"""
+class OFPadMember (namedtuple('OFPadMember', ['pad_length', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
+ pass
+
+"""
+An OpenFlow enumeration
+
+All values are Python ints.
+
+@param name
+@param entries List of OFEnumEntry objects in input order
+@params dict of optional params. Currently defined:
+ - wire_type: the low_level type of the enum values (uint8,...)
+"""
+class OFEnum(namedtuple('OFEnum', ['name', 'entries', 'params'])):
+ def __init__(self, *a, **kw):
+ super(OFEnum, self).__init__(*a, **kw)
+ # Back reference will be added by assignment
+ self.protocol = None
+
+ @property
+ def values(self):
+ return [(e.name, e.value) for e in self.entries]
+
+ @property
+ def is_bitmask(self):
+ return "bitmask" in self.params and self.params['bitmask']
+
+ @property
+ def wire_type(self):
+ return self.params['wire_type'] if 'wire_type' in self.params else self.name
+
+class OFEnumEntry(namedtuple('OFEnumEntry', ['name', 'value', 'params'])):
+ def __init__(self, *a, **kw):
+ super(OFEnumEntry, self).__init__(*a, **kw)
+ # Back reference will be added by assignment
+ self.enum = None
+
+class RedefinedException(Exception):
+ pass
+
+class ClassNotFoundException(Exception):
+ pass
+
+class DependencyCycleException(Exception):
+ pass
+
+def build_protocol(version, ofinputs):
+ name_frontend_classes = OrderedDict()
+ name_frontend_enums = OrderedDict()
+
+ for ofinput in ofinputs:
+ for c in ofinput.classes:
+ name = c.name
+ if name in name_frontend_classes:
+ raise RedefinedException("Error parsing {}. Class {} redefined (already defined in {})"
+ .format(ofinput.filename, name,
+ name_frontend_classes[name][1].filename))
+ else:
+ name_frontend_classes[name] = (c, ofinput)
+ for e in ofinput.enums:
+ name = e.name
+ if name in name_frontend_enums:
+ raise RedefinedException("Error parsing {}. Enum {} redefined (already defined in {})"
+ .format(ofinput.filename, name,
+ name_frontend_enums[name][1].filename))
+ else:
+ name_frontend_enums[name] = (e, ofinput)
+
+ name_enums = {}
+ for fe, _ in name_frontend_enums.values():
+ entries = tuple(OFEnumEntry(name=e.name, value=e.value,
+ params=e.params) for e in fe.entries)
+ enum = OFEnum(name=fe.name,
+ entries=entries,
+ params=fe.params)
+ for e in entries:
+ e.enum = enum
+ name_enums[enum.name] = enum
+
+ name_classes = OrderedDict()
+ build_touch_classes = OrderedSet()
+
+ def convert_member_properties(props):
+ return { name if name != "length" else "pad_length" : value for name, value in props.items() }
+
+ def build_member(of_class, fe_member, length_info):
+ ir_class = globals()[type(fe_member).__name__]
+ member = ir_class(offset = length_info.offset,
+ base_length = length_info.base_length,
+ is_fixed_length=length_info.is_fixed_length,
+ **convert_member_properties(vars(fe_member)))
+ member.of_class = of_class
+ return member
+
+ def build_class(name):
+ if name in name_classes:
+ return name_classes[name]
+ if name in build_touch_classes:
+ raise DependencyCycleException( "Dependency cycle: {}"
+ .format(" -> ".join(list(build_touch_classes) + [name])))
+ if not name in name_frontend_classes:
+ raise ClassNotFoundException("Class not found: {}".format(name))
+
+ build_touch_classes.add(name)
+
+ fe, _ = name_frontend_classes[name]
+
+ superclass = build_class(fe.superclass) if fe.superclass else None
+
+ # make sure members on which we depend are built first (for calc_length)
+ for m in fe.members:
+ if not hasattr(m, "oftype"):
+ continue
+ for m_name in re.sub(r'_t$', '', m.oftype), m.oftype:
+ logger.debug("Checking {}".format(m_name))
+ if m_name in name_frontend_classes:
+ build_class(m_name)
+
+ base_length, is_fixed_length, member_lengths = \
+ ir_offset.calc_lengths(version, fe, name_classes, name_enums)
+
+ members = []
+ c = OFClass(name=fe.name, superclass=superclass,
+ members=members, virtual=fe.virtual, params=fe.params,
+ is_fixed_length=is_fixed_length, base_length=base_length)
+
+ members.extend( build_member(c, fe_member, member_lengths[fe_member])
+ for fe_member in fe.members)
+
+ name_classes[name] = c
+ build_touch_classes.remove(name)
+ return c
+
+ for name in sorted(name_frontend_classes.keys()):
+ c = build_class(name)
+
+ protocol = OFProtocol(version=version, classes=tuple(name_classes.values()), enums=tuple(name_enums.values()))
+ for e in chain(protocol.classes, protocol.enums):
+ e.protocol = protocol
+ return protocol