| # 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) |
| |
| def inheritance_root(self): |
| if not self.superclass: |
| if self.virtual: |
| return self |
| else: |
| return None |
| else: |
| return self.superclass.inheritance_root() |
| |
| @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_oxs(self): |
| return self.is_instanceof("of_oxs") |
| |
| @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 length_member(self): |
| return find(lambda m: type(m) == OFLengthMember, self.members) |
| |
| @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 |
| |
| ofc_members = [] |
| for m in orig_fe.members: |
| if not isinstance(m, frontend_ir.OFDataMember) and not isinstance(m, frontend_ir.OFPadMember): |
| ofc_members.append(m) |
| |
| fe = frontend_ir.OFClass( |
| name=name, |
| superclass=superclass_name, |
| members=ofc_members, |
| 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 |