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_front_end/frontend.py b/loxi_front_end/frontend.py
index f4dece5..1e1b1de 100644
--- a/loxi_front_end/frontend.py
+++ b/loxi_front_end/frontend.py
@@ -28,9 +28,8 @@
from generic_utils import find
from collections import namedtuple
import copy
-import of_g
-import loxi_front_end.type_maps as type_maps
-from loxi_ir import *
+import loxi_globals
+import loxi_front_end.frontend_ir as ir
class InputError(Exception):
pass
@@ -46,23 +45,23 @@
def create_member(m_ast, ctx):
if m_ast[0] == 'pad':
- return OFPadMember(length=m_ast[1])
+ return ir.OFPadMember(length=m_ast[1])
elif m_ast[0] == 'type':
- return OFTypeMember(name=m_ast[2], oftype=get_type(m_ast[1], ctx), value=m_ast[3])
+ return ir.OFTypeMember(name=m_ast[2], oftype=get_type(m_ast[1], ctx), value=m_ast[3])
elif m_ast[0] == 'data':
if m_ast[2] == 'length' or m_ast[2] == 'len': # Should be moved to parser
- return OFLengthMember(name=m_ast[2], oftype=get_type(m_ast[1], ctx))
+ return ir.OFLengthMember(name=m_ast[2], oftype=get_type(m_ast[1], ctx))
elif m_ast[2] == 'actions_len':
# HACK only usage so far
- return OFFieldLengthMember(name=m_ast[2], oftype=get_type(m_ast[1], ctx), field_name='actions')
+ return ir.OFFieldLengthMember(name=m_ast[2], oftype=get_type(m_ast[1], ctx), field_name='actions')
else:
- return OFDataMember(name=m_ast[2], oftype=get_type(m_ast[1], ctx))
+ return ir.OFDataMember(name=m_ast[2], oftype=get_type(m_ast[1], ctx))
elif m_ast[0] == 'discriminator':
- return OFDiscriminatorMember(name=m_ast[2], oftype=get_type(m_ast[1], ctx))
+ return ir.OFDiscriminatorMember(name=m_ast[2], oftype=get_type(m_ast[1], ctx))
else:
raise InputError("Dont know how to create member: %s" % m_ast[0])
-def create_ofinput(ast):
+def create_ofinput(filename, ast):
"""
Create an OFInput from an AST
@@ -72,7 +71,7 @@
@returns An OFInput object
"""
ctx = FrontendCtx(set())
- ofinput = OFInput(wire_versions=set(), classes=[], enums=[])
+ ofinput = ir.OFInput(filename, wire_versions=set(), classes=[], enums=[])
for decl_ast in ast:
if decl_ast[0] == 'struct':
@@ -84,11 +83,11 @@
superclass = decl_ast[3]
members = [create_member(m_ast, ctx) for m_ast in decl_ast[4]]
- discriminators = [ m for m in members if isinstance(m, OFDiscriminatorMember) ]
+ discriminators = [ m for m in members if isinstance(m, ir.OFDiscriminatorMember) ]
if len(discriminators) > 1:
raise InputError("%s: Cannot support more than one discriminator by class - got %s" %
(decl_ast[1], repr(discriminators)))
- ofclass = OFClass(name=decl_ast[1], members=members, superclass=superclass,
+ ofclass = ir.OFClass(name=decl_ast[1], members=members, superclass=superclass,
virtual = len(discriminators) > 0,
params = { param: value for param, value in decl_ast[2] })
ofinput.classes.append(ofclass)
@@ -97,16 +96,16 @@
# 1: name
# 2: potentially list of [param_name, param_value]
# 3: list of [constant_name, constant_value]+
- enum = OFEnum(name=decl_ast[1],
- entries=[OFEnumEntry(name=x[0], value=x[2], params={param:value for param, value in x[1] }) for x in decl_ast[3]],
+ enum = ir.OFEnum(name=decl_ast[1],
+ entries=[ir.OFEnumEntry(name=x[0], value=x[2], params={param:value for param, value in x[1] }) for x in decl_ast[3]],
params = { param: value for param, value in decl_ast[2] }
)
ofinput.enums.append(enum)
elif decl_ast[0] == 'metadata':
if decl_ast[1] == 'version':
if decl_ast[2] == 'any':
- ofinput.wire_versions.update(of_g.wire_ver_map.keys())
- elif int(decl_ast[2]) in of_g.supported_wire_protos:
+ ofinput.wire_versions.update(v.wire_version for v in loxi_globals.OFVersions.all_supported)
+ elif int(decl_ast[2]) in loxi_globals.OFVersions.wire_version_map:
ofinput.wire_versions.add(int(decl_ast[2]))
else:
raise InputError("Unrecognized wire protocol version %r" % decl_ast[2])
diff --git a/loxi_ir.py b/loxi_front_end/frontend_ir.py
similarity index 74%
rename from loxi_ir.py
rename to loxi_front_end/frontend_ir.py
index a70f041..a927f94 100644
--- a/loxi_ir.py
+++ b/loxi_front_end/frontend_ir.py
@@ -28,11 +28,9 @@
from generic_utils import find
from collections import namedtuple
-# This module is intended to be imported like this: from loxi_ir import *
-# All public names are prefixed with 'OF'.
+# This module is represents the frontend IR.
__all__ = [
'OFInput',
- 'OFProtocol',
'OFClass',
'OFDataMember',
'OFTypeMember',
@@ -51,23 +49,7 @@
@param classes List of OFClass objects in the same order as in the file
@param enums List of Enum objects in the same order as in the file
"""
-OFInput = namedtuple('OFInput', ['wire_versions', 'classes', 'enums'])
-
-"""
-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', ['wire_version', 'classes', 'enums'])):
- 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)
+OFInput = namedtuple('OFInput', ['filename', 'wire_versions', 'classes', 'enums'])
"""
An OpenFlow class
@@ -82,13 +64,7 @@
@param members List of *Member objects
@param params optional dictionary of parameters
"""
-class OFClass(namedtuple('OFClass', ['name', 'superclass', 'members', 'virtual', 'params'])):
- 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)
+OFClass = namedtuple('OFClass', ['name', 'superclass', 'members', 'virtual', 'params'])
"""
Normal field
@@ -161,14 +137,5 @@
@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'])):
- @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']
-
-
+OFEnum = namedtuple('OFEnum', ['name', 'entries', 'params'])
OFEnumEntry = namedtuple('OFEnumEntry', ['name', 'value', 'params'])
diff --git a/loxi_ir/__init__.py b/loxi_ir/__init__.py
new file mode 100644
index 0000000..b4f1c05
--- /dev/null
+++ b/loxi_ir/__init__.py
@@ -0,0 +1,31 @@
+# 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.
+
+# Import the model
+from ir import *
+from ir import build_protocol
+from unified import build_unified_ir
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
diff --git a/loxi_ir/ir_offset.py b/loxi_ir/ir_offset.py
new file mode 100644
index 0000000..24589b2
--- /dev/null
+++ b/loxi_ir/ir_offset.py
@@ -0,0 +1,196 @@
+## List of mixed data types
+#
+# This is a list of data types which require special treatment
+# because the underlying datatype has changed between versions.
+# The main example is port which went from 16 to 32 bits. We
+# define per-version accessors for these types and those are
+# used in place of the normal ones.
+#
+# The wire protocol number is used to identify versions. For now,
+# the value is the name of the type to use for that version
+#
+# This is the map between the external type (like of_port_no_t)
+# which is used by customers of this code and the internal
+# datatypes (like uint16_t) that appear on the wire for a
+# particular version.
+#
+from collections import namedtuple
+import logging
+
+import loxi_front_end.frontend_ir as fe
+import loxi_ir.ir
+
+ofp_constants = dict(
+ OF_MAX_TABLE_NAME_LEN = 32,
+ OF_MAX_PORT_NAME_LEN = 16,
+ OF_ETH_ALEN = 6,
+ OF_DESC_STR_LEN = 256,
+ OF_SERIAL_NUM_LEN = 32
+)
+
+
+of_mixed_types = dict(
+ of_port_no_t = {
+ 1: "uint16_t",
+ 2: "uint32_t",
+ 3: "uint32_t",
+ 4: "uint32_t",
+ "short_name":"port_no"
+ },
+ of_port_desc_t = {
+ 1: "of_port_desc_t",
+ 2: "of_port_desc_t",
+ 3: "of_port_desc_t",
+ 4: "of_port_desc_t",
+ "short_name":"port_desc"
+ },
+ of_bsn_vport_t = {
+ 1: "of_bsn_vport_t",
+ 2: "of_bsn_vport_t",
+ 3: "of_bsn_vport_t",
+ 4: "of_bsn_vport_t",
+ "short_name":"bsn_vport"
+ },
+ of_fm_cmd_t = { # Flow mod command went from u16 to u8
+ 1: "uint16_t",
+ 2: "uint8_t",
+ 3: "uint8_t",
+ 4: "uint8_t",
+ "short_name":"fm_cmd"
+ },
+ of_wc_bmap_t = { # Wildcard bitmap
+ 1: "uint32_t",
+ 2: "uint32_t",
+ 3: "uint64_t",
+ 4: "uint64_t",
+ "short_name":"wc_bmap"
+ },
+ of_match_bmap_t = { # Match bitmap
+ 1: "uint32_t",
+ 2: "uint32_t",
+ 3: "uint64_t",
+ 4: "uint64_t",
+ "short_name":"match_bmap"
+ },
+ of_match_t = { # Match object
+ 1: "of_match_v1_t",
+ 2: "of_match_v2_t",
+ 3: "of_match_v3_t",
+ 4: "of_match_v3_t", # Currently uses same match as 1.2 (v3).
+ "short_name":"match"
+ },
+)
+
+## basic lengths
+of_base_lengths = dict(
+ char = (1, True),
+ uint8_t = (1, True),
+ uint16_t = (2, True),
+ uint32_t = (4, True),
+ uint64_t = (8, True),
+ of_mac_addr_t = (6, True),
+ of_ipv4_t = (4, True),
+ of_ipv6_t = (16, True),
+ of_port_name_t = (ofp_constants["OF_MAX_PORT_NAME_LEN"], True),
+ of_table_name_t = (ofp_constants["OF_MAX_TABLE_NAME_LEN"], True),
+ of_desc_str_t = (ofp_constants["OF_DESC_STR_LEN"], True),
+ of_serial_num_t = (ofp_constants["OF_SERIAL_NUM_LEN"], True),
+ of_match_v1_t = (40, True),
+ of_match_v2_t = (88, True),
+ of_match_v3_t = (8, False),
+ of_octets_t = (0, False),
+ of_bitmap_128_t = (16, True),
+)
+
+def type_dec_to_count_base(m_type):
+ """
+ Resolve a type declaration like uint8_t[4] to a count (4) and base_type
+ (uint8_t)
+
+ @param m_type The string type declaration to process
+ """
+ count = 1
+ chk_ar = m_type.split('[')
+ if len(chk_ar) > 1:
+ count_str = chk_ar[1].split(']')[0]
+ if count_str in ofp_constants:
+ count = ofp_constants[count_str]
+ else:
+ count = int(count_str)
+ base_type = chk_ar[0]
+ else:
+ base_type = m_type
+ return count, base_type
+
+
+LengthInfo = namedtuple("LengthInfo", ("offset", "base_length", "is_fixed_length"))
+
+def calc_lengths(version, fe_class, existing_classes, existing_enums):
+ offset_fixed = True
+ offset = 0
+
+ member_infos = {}
+ for member in fe_class.members:
+ member_offset = offset if offset_fixed else None
+
+ if isinstance(member, fe.OFPadMember):
+ member_base_length = member.length
+ member_fixed_length = True
+ else:
+ m_type = member.oftype
+ name = member.name
+
+ member_base_length = 0
+ if m_type.find("list(") == 0:
+ member_fixed_length = False
+ elif m_type.find("struct") == 0:
+ raise Exception("Error: recursive struct found: {}, {}"
+ .format(fe_class.name, name))
+ elif m_type == "octets":
+ member_fixed_length = False
+ else:
+ member_base_length, member_fixed_length = member_length(version, fe_class, member, existing_classes, existing_enums)
+
+ if not member_fixed_length:
+ offset_fixed = False
+
+ member_infos[member] = LengthInfo(member_offset, member_base_length,
+ member_fixed_length)
+ offset += member_base_length
+
+ base_length = offset
+ fixed_length = offset_fixed if not fe_class.virtual else False
+ return (base_length, fixed_length, member_infos)
+
+def member_length(version, fe_class, fe_member, existing_classes, existing_enums):
+ """
+ return the length of an ir member.
+
+ @return tuple (base_length, length_fixed)
+ """
+ count, base_type = type_dec_to_count_base(fe_member.oftype)
+
+ len_update = 0
+ if base_type in of_mixed_types:
+ base_type = of_mixed_types[base_type][version.wire_version]
+
+ base_class = base_type[:-2]
+ if base_class in existing_classes:
+ member_ir_class = existing_classes[base_class]
+ bytes = member_ir_class.base_length
+ length_fixed = member_ir_class.is_fixed_length
+ else:
+ if base_type in existing_enums:
+ enum = existing_enums[base_type]
+ base_type = enum.wire_type
+
+ if base_type in of_base_lengths:
+ bytes, length_fixed = of_base_lengths[base_type]
+ else:
+ raise Exception("Unknown type for {}.{}: {}".format(fe_class.name, fe_member.name, base_type))
+
+ return (count * bytes), length_fixed
+
+def calculate_offsets_and_lengths(protocol):
+ for ir_class in protocol.classes:
+ offset_and_length_for_class(ir_class)
diff --git a/loxi_ir/unified.py b/loxi_ir/unified.py
new file mode 100644
index 0000000..d481730
--- /dev/null
+++ b/loxi_ir/unified.py
@@ -0,0 +1,155 @@
+#!/usr/bin/env python
+# 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.
+
+import copy
+from collections import OrderedDict
+from itertools import chain
+import logging
+
+import ir
+
+def build_unified_ir(name_protocol_map):
+ class UnifiedClassSpec(object):
+ def __init__(self, name):
+ self.name = name
+ self.members = OrderedDict()
+ self.superclass_name = None
+ self.superclass_set = False
+ self.params = OrderedDict()
+ self.version_class = OrderedDict()
+ self.virtual = False
+ self.base_length = None
+ self.is_fixed_length = True
+
+ def add_class(self, version, v_class):
+ for v_member in v_class.members:
+ if hasattr(v_member, "name"):
+ if not v_member.name in self.members:
+ self.members[v_member.name] = v_member
+ else:
+ if not type(self.members[v_member.name]) == type(v_member):
+ raise Exception("Error unifying ir class {} - adding version: member_type {} <-> {}".format(
+ self.name, v_class.protocol.version, unified_members[v_member.name], v_member))
+
+ if not self.superclass_set:
+ self.superclass_name = v_class.superclass.name if v_class.superclass else None
+ else:
+ if self.superclass_name != v_class.superclass_name:
+ raise Exception("Error unifying ir class {} - superclass: param {} <-> {}".format(
+ self.name, v_class.protocol.version, self.superclass_name, v_class.superclass_name))
+
+ for name, value in v_class.params.items():
+ if not name in self.params:
+ self.params[name] = value
+ else:
+ if self.params[name] != value:
+ raise Exception("Error unifying ir class {} - adding version: param {} <-> {}".format(
+ self.name, v_class.protocol.version, self.params[name], value))
+
+ if v_class.virtual:
+ self.virtual = True
+
+ if not v_class.is_fixed_length:
+ self.is_fixed_length = False
+
+ if self.base_length is None:
+ self.base_length = v_class.base_length
+ elif self.base_length != v_class.base_length:
+ self.is_fixed_length = False
+ if self.base_length > v_class.base_length:
+ self.base_length = v_class.base_length
+ self.version_class[version] = v_class
+
+ class UnifiedEnumSpec(object):
+ def __init__(self, name):
+ self.name = name
+ self.entries = {}
+ self.params = {}
+ self.version_enums = OrderedDict()
+
+ def add_enum(self, version, v_enum):
+ for e in v_enum.entries:
+ if not e.name in self.entries:
+ self.entries[e.name] = ir.OFEnumEntry(e.name, e.value, copy.copy(e.params))
+ else:
+ entry = self.entries[e.name]
+ for name, value in e.params.items():
+ if not name in entry.params:
+ entry.params[name] = value
+ elif entry.params[name] != value:
+ raise Exception("Error unifying ir enum {} - adding version: param {} <-> {}".format(
+ self.name, entry.params[name], value))
+ for name, value in v_enum.params.items():
+ if not name in self.params:
+ self.params[name] = value
+ else:
+ if self.params[name] != value:
+ if name == "wire_type":
+ self.params[name] = None
+ else:
+ raise Exception("Error unifying ir enum {} - adding version: {} param {} <-> {}".format(
+ self.name, v_enum.protocol.version, self.params[name], value))
+
+ self.version_enums[version]=v_enum
+
+ u_name_classes = OrderedDict()
+ u_name_enums = OrderedDict()
+
+ for version, protocol in name_protocol_map.items():
+ assert isinstance(version, ir.OFVersion)
+ for v_class in protocol.classes:
+ name = v_class.name
+ if not name in u_name_classes:
+ u_name_classes[name] = UnifiedClassSpec(name)
+ spec = u_name_classes[name]
+ spec.add_class(version, v_class)
+
+ for v_enum in protocol.enums:
+ name = v_enum.name
+ if not name in u_name_enums:
+ u_name_enums[name] = UnifiedEnumSpec(name)
+ spec = u_name_enums[name]
+ spec.add_enum(version, v_enum)
+
+ unified_enums = tuple(ir.OFEnum(name=s.name, entries=tuple(s.entries.values()), params=s.params) for s in u_name_enums.values())
+ unified_classes = OrderedDict()
+ for name, spec in u_name_classes.items():
+ u = ir.OFUnifiedClass(
+ name = spec.name,
+ version_classes=spec.version_class,
+ superclass=None if not spec.superclass_name else unified_classes[spec.superclass_name],
+ members=spec.members.values(),
+ virtual=spec.virtual,
+ params=spec.params,
+ base_length=spec.base_length,
+ is_fixed_length=spec.is_fixed_length)
+ unified_classes[name] = u
+
+ unified = ir.OFProtocol(version=None, classes = tuple(unified_classes.values()), enums=unified_enums)
+ for e in chain(unified.classes, unified.enums):
+ e.protocol = unified
diff --git a/utest/test_build_ir.py b/utest/test_build_ir.py
new file mode 100755
index 0000000..b6d87a3
--- /dev/null
+++ b/utest/test_build_ir.py
@@ -0,0 +1,232 @@
+#!/usr/bin/env python
+# 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.
+
+import sys
+import os
+import unittest
+
+from nose.tools import eq_, ok_, raises
+
+root_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..')
+sys.path.insert(0, root_dir)
+
+import loxi_ir.ir as ir
+import loxi_front_end.frontend_ir as fe
+
+class BuildIRTest(unittest.TestCase):
+
+ def test_simple(self):
+ version = ir.OFVersion("1.0", 1)
+ input = fe.OFInput(filename="test.dat",
+ wire_versions=(1,),
+ classes=(
+ fe.OFClass(name="OFMessage",
+ superclass=None,
+ members=(
+ fe.OFDataMember(name='version', oftype='uint32_t'),
+ fe.OFLengthMember(name='length', oftype='uint16_t')
+ ),
+ virtual=False,
+ params={}
+ ),
+ ),
+ enums=()
+ )
+
+ p = ir.build_protocol(version, [ input ])
+ eq_(1, len(p.classes))
+ c = p.classes[0]
+ eq_("OFMessage", c.name)
+ eq_(None, c.superclass)
+ eq_(False, c.virtual)
+ eq_({}, c.params)
+ eq_(2, len(c.members))
+ eq_(p, c.protocol)
+
+ m1 = c.members[0]
+ ok_(isinstance(m1, ir.OFDataMember))
+ eq_("version", m1.name)
+ eq_("uint32_t", m1.oftype)
+ eq_(4, m1.length)
+ eq_(True, m1.is_fixed_length)
+ eq_(0, m1.offset)
+ eq_(c, m1.of_class)
+
+ m2 = c.members[1]
+ ok_(isinstance(m2, ir.OFLengthMember))
+ eq_("length", m2.name)
+ eq_("uint16_t", m2.oftype)
+ eq_(2, m2.length)
+ eq_(True, m2.is_fixed_length)
+ eq_(4, m2.offset)
+ eq_(c, m2.of_class)
+
+ eq_(True, c.is_fixed_length)
+ eq_(6, c.length)
+
+ def test_resolve_superclass(self):
+ version = ir.OFVersion("1.0", 1)
+ input = fe.OFInput(filename="test.dat",
+ wire_versions=(1,),
+ classes=(
+ fe.OFClass(name="OFMessage",
+ superclass=None,
+ members=(),
+ virtual=True,
+ params={}
+ ),
+ fe.OFClass(name="OFHello",
+ superclass="OFMessage",
+ members=(),
+ virtual=False,
+ params={}
+ ),
+ ),
+ enums=()
+ )
+ p = ir.build_protocol(version, [ input ])
+ eq_(2, len(p.classes))
+ c, c2 = p.classes
+ eq_("OFMessage", c.name)
+ eq_(None, c.superclass)
+ eq_(True, c.virtual)
+ eq_("OFHello", c2.name)
+ eq_(c, c2.superclass)
+ eq_(False, c2.virtual)
+
+ @raises(ir.ClassNotFoundException)
+ def test_resolve_superclass(self):
+ version = ir.OFVersion("1.0", 1)
+ input = fe.OFInput(filename="test.dat",
+ wire_versions=(1,),
+ classes=(
+ fe.OFClass(name="OFMessage",
+ superclass="NotFoundSuperClass",
+ members=(),
+ virtual=True,
+ params={}
+ ),
+ ),
+ enums=()
+ )
+ p = ir.build_protocol(version, [ input ])
+
+
+ @raises(ir.DependencyCycleException)
+ def test_dependency_cycle(self):
+ version = ir.OFVersion("1.0", 1)
+ input = fe.OFInput(filename="test.dat",
+ wire_versions=(1,),
+ classes=(
+ fe.OFClass(name="OFMessage",
+ superclass="OFHeader",
+ members=(),
+ virtual=True,
+ params={}
+ ),
+ fe.OFClass(name="OFHeader",
+ superclass="OFMessage",
+ members=(),
+ virtual=True,
+ params={}
+ ),
+ ),
+ enums=()
+ )
+ p = ir.build_protocol(version, [ input ])
+
+ @raises(ir.RedefinedException)
+ def test_class_redefined(self):
+ version = ir.OFVersion("1.0", 1)
+ inputs = (
+ fe.OFInput(filename="test.dat",
+ wire_versions=(1,),
+ classes=(
+ fe.OFClass(name="OFMessage",
+ superclass=None,
+ members=(),
+ virtual=True,
+ params={}
+ ),
+ ),
+ enums=(),
+ ),
+ fe.OFInput(filename="test2.dat",
+ wire_versions=(1,),
+ classes=(
+ fe.OFClass(name="OFMessage",
+ superclass=None,
+ members=(),
+ virtual=True,
+ params={}
+ ),
+ ),
+ enums=()
+ )
+ )
+ p = ir.build_protocol(version, inputs)
+
+
+ def test_enums(self):
+ version = ir.OFVersion("1.0", 1)
+ input = fe.OFInput(filename="test.dat",
+ wire_versions=(1,),
+ classes=(),
+ enums=(
+ fe.OFEnum(name='ofp_flow_wildcards',
+ entries=(fe.OFEnumEntry(name="OFPFW_IN_PORT", value=0x01, params={}),
+ fe.OFEnumEntry(name="OFPFW_DL_VLAN", value=0x2, params={})),
+ params = dict(wire_type="uint32_t", bitmask=True)
+ ),
+ fe.OFEnum(name='ofp_queue_properties',
+ entries=(fe.OFEnumEntry(name="OFPQT_NONE", value=0x00, params={}),
+ fe.OFEnumEntry(name="OFPQT_MIN_RATE", value=0x1, params={})),
+ params = dict(wire_type="uint32_t")
+ ),
+ )
+ )
+
+ p = ir.build_protocol(version, [ input ])
+ eq_(0, len(p.classes))
+ eq_(2, len(p.enums))
+ e = p.enums[0]
+ eq_("ofp_flow_wildcards", e.name)
+ eq_(True, e.is_bitmask)
+ eq_("uint32_t", e.wire_type)
+ eq_(ir.OFEnumEntry(name="OFPFW_IN_PORT", value=0x01, params={}), e.entries[0])
+ eq_(ir.OFEnumEntry(name="OFPFW_DL_VLAN", value=0x02, params={}), e.entries[1])
+
+ e = p.enums[1]
+ eq_("ofp_queue_properties", e.name)
+ eq_(False, e.is_bitmask)
+ eq_("uint32_t", e.wire_type)
+ eq_(ir.OFEnumEntry(name="OFPQT_NONE", value=0x00, params={}), e.entries[0])
+ eq_(ir.OFEnumEntry(name="OFPQT_MIN_RATE", value=0x01, params={}), e.entries[1])
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/utest/test_frontend.py b/utest/test_frontend.py
index 8febfde..cfadc39 100755
--- a/utest/test_frontend.py
+++ b/utest/test_frontend.py
@@ -35,7 +35,7 @@
import loxi_front_end.parser as parser
import loxi_front_end.frontend as frontend
-from loxi_ir import *
+from loxi_front_end.frontend_ir import *
class FrontendTests(unittest.TestCase):
maxDiff = None
@@ -109,7 +109,7 @@
]
self.assertEquals(expected_ast, ast)
- ofinput = frontend.create_ofinput(ast)
+ ofinput = frontend.create_ofinput("standard-1.0", ast)
self.assertEquals(set([1, 2]), ofinput.wire_versions)
expected_classes = [
OFClass(name='of_echo_reply', superclass=None, members=[
@@ -180,7 +180,7 @@
]
self.assertEquals(expected_ast, ast)
- ofinput = frontend.create_ofinput(ast)
+ ofinput = frontend.create_ofinput("standard-1.0", ast)
expected_classes = [
OFClass(name='of_queue_prop', superclass=None, members=[
OFDiscriminatorMember('type', 'uint16_t'),