blob: 6fae92b18900646d351a07ffced17c1eec7cfc6d [file] [log] [blame]
# 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
import loxi_front_end.frontend_ir as frontend_ir
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: {}".format(self.name))
@property
def has_internal_alignment(self):
return self.params.get('length_includes_align') == 'True'
@property
def has_external_alignment(self):
return self.params.get('length_includes_align') == 'False'
@property
def has_type_members(self):
return find(lambda m: isinstance(m, OFTypeMember), self.members) is not None
""" 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(self, 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):
if isinstance(fe_member, frontend_ir.OFVersionMember):
member = OFTypeMember(offset = length_info.offset,
base_length = length_info.base_length,
is_fixed_length=length_info.is_fixed_length,
value = version.wire_version,
**convert_member_properties(fe_member._asdict()))
else:
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(fe_member._asdict()))
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
def build_id_class(orig_name, base_name):
name = base_name + '_id' + orig_name[len(base_name):]
if name in name_classes:
return name_classes[name]
orig_fe, _ = name_frontend_classes[orig_name]
if orig_fe.superclass:
superclass_name = base_name + '_id' + orig_fe.superclass[len(base_name):]
superclass = build_id_class(orig_fe.superclass, base_name)
else:
superclass_name = None
superclass = None
fe = frontend_ir.OFClass(
name=name,
superclass=superclass_name,
members=[m for m in orig_fe.members if not isinstance(m, frontend_ir.OFDataMember)],
virtual=orig_fe.virtual,
params={})
base_length, is_fixed_length, member_lengths = \
ir_offset.calc_lengths(version, fe, name_classes, name_enums)
assert fe.virtual or is_fixed_length
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
return c
id_class_roots = ["of_action", "of_instruction"]
for name in sorted(name_frontend_classes.keys()):
c = build_class(name)
# Build ID classes for OF 1.3+
if version.wire_version >= 4:
for root in id_class_roots:
if c.is_instanceof(root):
build_id_class(name, root)
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