blob: 410a1dd5e2679f1d9ff309a910da65a85262a243 [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.
# Prototype of an Intermediate Object model for the java code generator
# A lot of this stuff could/should probably be merged with the python utilities
import collections
from collections import namedtuple, defaultdict, OrderedDict
import logging
import os
import pdb
import re
from generic_utils import find, memoize, OrderedSet, OrderedDefaultDict
import of_g
from loxi_ir import *
import loxi_front_end.type_maps as type_maps
import loxi_utils.loxi_utils as loxi_utils
import py_gen.util as py_utils
import test_data
import java_gen.java_type as java_type
from java_gen.java_type import erase_type_annotation
# Key is modified name of error code; value is OFErrorType value string
error_type_map = {}
def adjust_ir():
"""
For Java we change of_error_message to combine the 16-bit type and code
fields into a single 32-bit code field and we combine the per-error-type
code enums into a single ofp_error_code enum. This enables the generated
OFErrorMsg class to have a getCode method that can return all supported
error codes. Otherwise we'd need to do something like having per-error-type
subclasses of OFErrorMsg that had a getCode that returned the different
error codes for each error type, which would be less convenient for clients
and would also entail changing the LOXI OF input files and impacting other
language backends.
"""
for version in of_g.target_version_list:
of_protocol = of_g.ir[version]
error_type = find(lambda e: e.name == "ofp_error_type", of_protocol.enums)
if error_type == None:
raise Exception("ofp_error_type enum not found; OF version: " + str(version))
error_code_entries = []
# For each error type value look up the corresponding error code enum.
# Add those values to the combined entries for the new ofp_error_code
# enum. The name of the new value is formed by concatenating the name
# of the error type value with the name of the old error code value.
for error_type_entry in error_type.entries:
# Strip off the OFPxx prefix
prefix_length = error_type_entry.name.find('_')
if prefix_length < 0:
raise Exception("OFPET prefix not found for ofp_error_type value " + error_type_entry.name + "; OF version: " + str(version))
error_type_entry_name = error_type_entry.name[prefix_length+1:]
if error_type_entry_name == "EXPERIMENTER":
# There isn't an error code enum defined for the experimenter error type
# FIXME: Need to add support for the message ofp_error_experimenter_msg format
continue
# The per-error-type code enums follow a naming conventions where
# the middle part of the enum name is the same as the name of the
# error type value (except lower-case).
error_code_enum_name = "ofp_" + error_type_entry_name.lower() + "_code"
# Look up the error code enum from the IR
error_code_enum = None
for i, enum in enumerate(of_protocol.enums):
if enum.name == error_code_enum_name:
error_code_enum = enum
# We don't want to generate code for the per-error-type
# enum so remove it from the IR
del of_protocol.enums[i]
break
if error_code_enum == None:
raise Exception("Error code enum not found: " + error_code_enum_name + "; OF version: " + str(version))
for error_code_entry in error_code_enum.entries:
# Strip off the prefix from the entry name
prefix_length = error_code_entry.name.find('_')
if prefix_length < 0:
raise Exception("Prefix not found for error code value " + error_code_entry.name + "; OF version: " + str(version))
error_code_entry_name = error_code_entry.name[prefix_length+1:]
# Combine the entry type name and the error code name
error_code_entry_name = error_type_entry_name + "_" + error_code_entry_name
# Combine the entry type value and the error code value
error_code_entry_value = (error_type_entry.value << 16) + error_code_entry.value
# Add the enum entry to the combined ofp_error_code
# Note that the "OFPEC" prefix is arbitrary. It will be stripped
# off again during Java code generation, but there needs to be
# some/any prefix
error_code_entries.append(OFEnumEntry("OFPEC_" + error_code_entry_name, error_code_entry_value, {}))
error_type_map[error_code_entry_name] = error_type_entry_name
# We've collected all of the entries. Now we can add the enum to the IR
of_protocol.enums.append(OFEnum("ofp_error_code", error_code_entries, {'wire_type': 'uint32_t'}))
# We also need to patch the of_error_msg class to combine the 16-bit error
# type and code fields into a single 32-bit error code field
error_msg = find(lambda c: c.name == "of_error_msg", of_protocol.classes)
if error_msg == None:
raise Exception("of_error_msg class not found; OF version: " + str(version))
err_type_index = None
for i, member in enumerate(error_msg.members):
if member.name == "err_type":
# Keep track of the error type index so we can remove it once
# we've finished iterating
err_type_index = i
elif member.name == 'code':
# Change the code to be a 32-bit ofp_error_code enum value
error_msg.members[i] = OFDataMember("code", "ofp_error_code")
if err_type_index == None:
raise Exception("err_type member of of_error_msg not found; OF version: " + str(version))
del error_msg.members[err_type_index]
def gen_error_type(enum_entry):
return "OFErrorType." + error_type_map[enum_entry.name]
class JavaModel(object):
# registry for enums that should not be generated
# set(${java_enum_name})
enum_blacklist = set(("OFDefinitions", "OFPortNo",))
# registry for enum *entry* that should not be generated
# map: ${java_enum_name} -> set(${java_entry_entry_name})
enum_entry_blacklist = defaultdict(lambda: set(), OFFlowWildcards=set([ "NW_DST_BITS", "NW_SRC_BITS", "NW_SRC_SHIFT", "NW_DST_SHIFT" ]))
# registry of interfaces that should not be generated
# set(java_names)
# OFUint structs are there for god-knows what in loci. We certainly don't need them.
interface_blacklist = set( ("OFUint8", "OFUint32",))
# registry of interface properties that should not be generated
# map: $java_type -> set(java_name_property)
read_blacklist = defaultdict(lambda: set(), OFExperimenter=set(('data','subtype')), OFActionExperimenter=set(('data',)))
# map: $java_type -> set(java_name_property)
write_blacklist = defaultdict(lambda: set(), OFOxm=set(('typeLen',)), OFAction=set(('type',)), OFInstruction=set(('type',)), OFFlowMod=set(('command', )), OFExperimenter=set(('data','subtype')), OFActionExperimenter=set(('data',)))
# interfaces that are virtual
virtual_interfaces = set(['OFOxm', 'OFInstruction', 'OFFlowMod', 'OFBsnVport' ])
OxmMapEntry = namedtuple("OxmMapEntry", ["type_name", "value", "masked" ])
oxm_map = { "OFOxmInPort": OxmMapEntry("OFPort", "IN_PORT", False),
"OFOxmInPortMasked": OxmMapEntry("OFPort", "IN_PORT", True),
"OFOxmInPhyPort": OxmMapEntry("OFPort", "IN_PHY_PORT", False),
"OFOxmInPhyPortMasked": OxmMapEntry("OFPort", "IN_PHY_PORT", True),
"OFOxmMetadata": OxmMapEntry("OFMetadata", "METADATA", False),
"OFOxmMetadataMasked": OxmMapEntry("OFMetadata", "METADATA", True),
"OFOxmEthDst": OxmMapEntry("MacAddress", "ETH_DST", False),
"OFOxmEthDstMasked": OxmMapEntry("MacAddress", "ETH_DST", True),
"OFOxmEthSrc": OxmMapEntry("MacAddress", "ETH_SRC", False),
"OFOxmEthSrcMasked": OxmMapEntry("MacAddress", "ETH_SRC", True),
"OFOxmEthType": OxmMapEntry("EthType", "ETH_TYPE", False),
"OFOxmEthTypeMasked": OxmMapEntry("EthType", "ETH_TYPE", True),
"OFOxmVlanVid": OxmMapEntry("VlanVid", "VLAN_VID", False),
"OFOxmVlanVidMasked": OxmMapEntry("VlanVid", "VLAN_VID", True),
"OFOxmVlanPcp": OxmMapEntry("VlanPcp", "VLAN_PCP", False),
"OFOxmVlanPcpMasked": OxmMapEntry("VlanPcp", "VLAN_PCP", True),
"OFOxmIpDscp": OxmMapEntry("IpDscp", "IP_DSCP", False),
"OFOxmIpDscpMasked": OxmMapEntry("IpDscp", "IP_DSCP", True),
"OFOxmIpEcn": OxmMapEntry("IpEcn", "IP_ECN", False),
"OFOxmIpEcnMasked": OxmMapEntry("IpEcn", "IP_ECN", True),
"OFOxmIpProto": OxmMapEntry("IpProtocol", "IP_PROTO", False),
"OFOxmIpProtoMasked": OxmMapEntry("IpProtocol", "IP_PROTO", True),
"OFOxmIpv4Src": OxmMapEntry("IPv4Address", "IPV4_SRC", False),
"OFOxmIpv4SrcMasked": OxmMapEntry("IPv4Address", "IPV4_SRC", True),
"OFOxmIpv4Dst": OxmMapEntry("IPv4Address", "IPV4_DST", False),
"OFOxmIpv4DstMasked": OxmMapEntry("IPv4Address", "IPV4_DST", True),
"OFOxmTcpSrc": OxmMapEntry("TransportPort", "TCP_SRC", False),
"OFOxmTcpSrcMasked": OxmMapEntry("TransportPort", "TCP_SRC", True),
"OFOxmTcpDst": OxmMapEntry("TransportPort", "TCP_DST", False),
"OFOxmTcpDstMasked": OxmMapEntry("TransportPort", "TCP_DST", True),
"OFOxmUdpSrc": OxmMapEntry("TransportPort", "UDP_SRC", False),
"OFOxmUdpSrcMasked": OxmMapEntry("TransportPort", "UDP_SRC", True),
"OFOxmUdpDst": OxmMapEntry("TransportPort", "UDP_DST", False),
"OFOxmUdpDstMasked": OxmMapEntry("TransportPort", "UDP_DST", True),
"OFOxmSctpSrc": OxmMapEntry("TransportPort", "SCTP_SRC", False),
"OFOxmSctpSrcMasked": OxmMapEntry("TransportPort", "SCTP_SRC", True),
"OFOxmSctpDst": OxmMapEntry("TransportPort", "SCTP_DST", False),
"OFOxmSctpDstMasked": OxmMapEntry("TransportPort", "SCTP_DST", True),
"OFOxmIcmpv4Type": OxmMapEntry("ICMPv4Type", "ICMPV4_TYPE", False),
"OFOxmIcmpv4TypeMasked": OxmMapEntry("ICMPv4Type", "ICMPV4_TYPE", True),
"OFOxmIcmpv4Code": OxmMapEntry("ICMPv4Code", "ICMPV4_CODE", False),
"OFOxmIcmpv4CodeMasked": OxmMapEntry("ICMPv4Code", "ICMPV4_CODE", True),
"OFOxmArpOp": OxmMapEntry("ArpOpcode", "ARP_OP", False),
"OFOxmArpOpMasked": OxmMapEntry("ArpOpcode", "ARP_OP", True),
"OFOxmArpSpa": OxmMapEntry("IPv4Address", "ARP_SPA", False),
"OFOxmArpSpaMasked": OxmMapEntry("IPv4Address", "ARP_SPA", True),
"OFOxmArpTpa": OxmMapEntry("IPv4Address", "ARP_TPA", False),
"OFOxmArpTpaMasked": OxmMapEntry("IPv4Address", "ARP_TPA", True),
"OFOxmArpSha": OxmMapEntry("MacAddress", "ARP_SHA", False),
"OFOxmArpShaMasked": OxmMapEntry("MacAddress", "ARP_SHA", True),
"OFOxmArpTha": OxmMapEntry("MacAddress", "ARP_THA", False),
"OFOxmArpThaMasked": OxmMapEntry("MacAddress", "ARP_THA", True),
"OFOxmIpv6Src": OxmMapEntry("IPv6Address", "IPV6_SRC", False),
"OFOxmIpv6SrcMasked": OxmMapEntry("IPv6Address", "IPV6_SRC", True),
"OFOxmIpv6Dst": OxmMapEntry("IPv6Address", "IPV6_DST", False),
"OFOxmIpv6DstMasked": OxmMapEntry("IPv6Address", "IPV6_DST", True),
"OFOxmIpv6Flabel": OxmMapEntry("IPv6FlowLabel", "IPV6_FLABEL", False),
"OFOxmIpv6FlabelMasked": OxmMapEntry("IPv6FlowLabel", "IPV6_FLABEL", True),
"OFOxmIcmpv6Type": OxmMapEntry("U8", "ICMPV6_TYPE", False),
"OFOxmIcmpv6TypeMasked": OxmMapEntry("U8", "ICMPV6_TYPE", True),
"OFOxmIcmpv6Code": OxmMapEntry("U8", "ICMPV6_CODE", False),
"OFOxmIcmpv6CodeMasked": OxmMapEntry("U8", "ICMPV6_CODE", True),
"OFOxmIpv6NdTarget": OxmMapEntry("IPv6Address", "IPV6_ND_TARGET", False),
"OFOxmIpv6NdTargetMasked": OxmMapEntry("IPv6Address", "IPV6_ND_TARGET", True),
"OFOxmIpv6NdSll": OxmMapEntry("MacAddress", "IPV6_ND_SLL", False),
"OFOxmIpv6NdSllMasked": OxmMapEntry("MacAddress", "IPV6_ND_SLL", True),
"OFOxmIpv6NdTll": OxmMapEntry("MacAddress", "IPV6_ND_TLL", False),
"OFOxmIpv6NdTllMasked": OxmMapEntry("MacAddress", "IPV6_ND_TLL", True),
"OFOxmMplsLabel": OxmMapEntry("U32", "MPLS_LABEL", False),
"OFOxmMplsLabelMasked": OxmMapEntry("U32", "MPLS_LABEL", True),
"OFOxmMplsTc": OxmMapEntry("U8", "MPLS_TC", False),
"OFOxmMplsTcMasked": OxmMapEntry("U8", "MPLS_TC", True)
}
# Registry of nullable properties:
# ${java_class_name} -> set(${java_property_name})
nullable_map = defaultdict(lambda: set(),
)
# represents a subgroup of a bitmask enum that is actualy a normal enumerable within a masked part of the enum
# e.g., the flags STP.* in OF1.0 port state are bit mask entries, but instead enumerables according to the mask "STP_MASK"
# name: a name for the group
# mask: java name of the enum entry that defines the mask
# members: set of names of the members of the group
MaskedEnumGroup = namedtuple("MaskedEnumGroup", ("name", "mask", "members"))
# registry of MaskedEnumGroups (see above).
# map: ${java_enum_name}: tuple(MaskedEnumGroup)
masked_enum_groups = defaultdict(lambda: (),
OFPortState = (MaskedEnumGroup("stp_flags", mask="STP_MASK", members=set(("STP_LISTEN", "STP_LEARN", "STP_FORWARD", "STP_BLOCK"))), ),
OFConfigFlags = (
MaskedEnumGroup("frag_flags", mask="FRAG_MASK", members=set(("FRAG_NORMAL", "FRAG_DROP", "FRAG_REASM"))),
),
OFTableConfig = (
MaskedEnumGroup("table_miss_flags", mask="TABLE_MISS_MASK", members=set(("TABLE_MISS_CONTROLLER", "TABLE_MISS_CONTINUE", "TABLE_MISS_DROP"))),
),
)
# represents a metadata property associated with an EnumClass
# name:
class OFEnumPropertyMetadata(namedtuple("OFEnumPropertyMetadata", ("name", "type", "value"))):
"""
represents a metadata property associated with an Enum Class
@param name name of metadata property
@param type java_type instance describing the type
@value: Generator function f(entry) that generates the value
"""
@property
def variable_name(self):
return self.name[0].lower() + self.name[1:]
@property
def getter_name(self):
prefix = "is" if self.type == java_type.boolean else "get"
return prefix+self.name
""" Metadata container. """
OFEnumMetadata = namedtuple("OFEnumMetadata", ("properties", "to_string"))
def gen_port_speed(enum_entry):
""" Generator function for OFortFeatures.PortSpeed"""
splits = enum_entry.name.split("_")
if len(splits)>=2:
m = re.match(r'\d+[MGTP]B', splits[1])
if m:
return "PortSpeed.SPEED_{}".format(splits[1])
return "PortSpeed.SPEED_NONE";
def gen_stp_state(enum_entry):
""" Generator function for OFPortState.StpState"""
splits = enum_entry.name.split("_")
if len(splits)>=1:
if splits[0] == "STP":
return "true"
return "false"
# registry for metadata properties for enums
# map: ${java_enum_name}: OFEnumMetadata
enum_metadata_map = defaultdict(lambda: JavaModel.OFEnumMetadata((), None),
OFPortFeatures = OFEnumMetadata((OFEnumPropertyMetadata("PortSpeed", java_type.port_speed, gen_port_speed),), None),
OFPortState = OFEnumMetadata((OFEnumPropertyMetadata("StpState", java_type.boolean, gen_stp_state),), None),
OFErrorCode = OFEnumMetadata((OFEnumPropertyMetadata("ErrorType", java_type.error_type, gen_error_type),), None),
)
@property
@memoize
def versions(self):
return OrderedSet( JavaOFVersion(raw_version) for raw_version in of_g.target_version_list )
@property
@memoize
def interfaces(self):
version_map_per_class = collections.OrderedDict()
for raw_version, of_protocol in of_g.ir.items():
jversion = JavaOFVersion(of_protocol.wire_version)
for of_class in of_protocol.classes:
if not of_class.name in version_map_per_class:
version_map_per_class[of_class.name] = collections.OrderedDict()
version_map_per_class[of_class.name][jversion] = of_class
interfaces = []
for class_name, version_map in version_map_per_class.items():
interfaces.append(JavaOFInterface(class_name, version_map))
interfaces = [ i for i in interfaces if i.name not in self.interface_blacklist ]
return interfaces
@memoize
def interface_by_name(self, name):
return find(lambda i: erase_type_annotation(i.name) == erase_type_annotation(name), self.interfaces)
@property
@memoize
def all_classes(self):
return [clazz for interface in self.interfaces for clazz in interface.versioned_classes]
@property
@memoize
def enums(self):
name_version_enum_map = OrderedDefaultDict(lambda: OrderedDict())
for version in self.versions:
of_protocol = of_g.ir[version.int_version]
for enum in of_protocol.enums:
name_version_enum_map[enum.name][version] = enum
enums = [ JavaEnum(name, version_enum_map) for name, version_enum_map,
in name_version_enum_map.items() ]
# inelegant - need java name here
enums = [ enum for enum in enums if enum.name not in self.enum_blacklist ]
return enums
@memoize
def enum_by_name(self, name):
res = find(lambda e: e.name == name, self.enums)
if not res:
raise KeyError("Could not find enum with name %s" % name)
return res
@property
@memoize
def of_factories(self):
prefix = "org.projectfloodlight.openflow.protocol"
factories = OrderedDict()
sub_factory_classes = ("OFAction", "OFInstruction", "OFMeterBand", "OFOxm", "OFQueueProp")
for base_class in sub_factory_classes:
package = base_class[2:].lower()
remove_prefix = base_class[2].lower() + base_class[3:]
# HACK need to have a better way to deal with parameterized base classes
annotated_base_class = base_class + "<?>" if base_class == "OFOxm" else base_class
factories[base_class] = OFFactory(package="%s.%s" % (prefix, package),
name=base_class + "s", members=[], remove_prefix=remove_prefix, base_class=annotated_base_class, sub_factories={}, xid_generator=False)
factories[""] = OFFactory(
package=prefix,
name="OFFactory",
remove_prefix="",
members=[], base_class="OFMessage", sub_factories=OrderedDict(
("{}{}s".format(n[2].lower(), n[3:]), "{}s".format(n)) for n in sub_factory_classes ),
xid_generator=True)
for i in self.interfaces:
for n, factory in factories.items():
if n == "":
factory.members.append(i)
break
else:
super_class = self.interface_by_name(n)
if i.is_instance_of(super_class):
factory.members.append(i)
break
return factories.values()
def generate_class(self, clazz):
""" return wether or not to generate implementation class clazz.
Now true for everything except OFTableModVer10.
@param clazz JavaOFClass instance
"""
if clazz.interface.name.startswith("OFMatchV"):
return True
elif clazz.name == "OFTableModVer10":
# tablemod ver 10 is a hack and has no oftype defined
return False
if loxi_utils.class_is_message(clazz.interface.c_name):
return True
if loxi_utils.class_is_oxm(clazz.interface.c_name):
return True
if loxi_utils.class_is_action(clazz.interface.c_name):
return True
if loxi_utils.class_is_instruction(clazz.interface.c_name):
return True
else:
return True
class OFFactory(namedtuple("OFFactory", ("package", "name", "members", "remove_prefix", "base_class", "sub_factories", "xid_generator"))):
@property
def factory_classes(self):
return [ OFFactoryClass(
package="org.projectfloodlight.openflow.protocol.ver{}".format(version.of_version),
name="{}Ver{}".format(self.name, version.of_version),
interface=self,
version=version
) for version in model.versions ]
def method_name(self, member, builder=True):
n = member.variable_name
if n.startswith(self.remove_prefix):
n = n[len(self.remove_prefix):]
n = n[0].lower() + n[1:]
if builder:
return "build" + n[0].upper() + n[1:]
else:
return n
OFGenericClass = namedtuple("OFGenericClass", ("package", "name"))
class OFFactoryClass(namedtuple("OFFactoryClass", ("package", "name", "interface", "version"))):
@property
def base_class(self):
return self.interface.base_class
@property
def versioned_base_class(self):
base_class_interface = model.interface_by_name(self.interface.base_class)
if base_class_interface and base_class_interface.has_version(self.version):
return base_class_interface.versioned_class(self.version)
else:
return None
model = JavaModel()
#######################################################################
### OFVersion
#######################################################################
class JavaOFVersion(object):
""" Models a version of OpenFlow. contains methods to convert the internal
Loxi version to a java constant / a string """
def __init__(self, int_version):
self.int_version = int(int_version)
@property
def of_version(self):
return "1" + str(int(self.int_version) - 1)
@property
def constant_version(self):
return "OF_" + self.of_version
def __repr__(self):
return "JavaOFVersion(%d)" % self.int_version
def __str__(self):
return of_g.param_version_names[self.int_version]
def __hash__(self):
return hash(self.int_version)
def __eq__(self, other):
if other is None or type(self) != type(other):
return False
return (self.int_version,) == (other.int_version,)
#######################################################################
### Interface
#######################################################################
class JavaOFInterface(object):
""" Models an OpenFlow Message class for the purpose of the java class.
Version agnostic, in contrast to the loxi_ir python model.
"""
def __init__(self, c_name, version_map):
""""
@param c_name: loxi style name (e.g., of_flow_add)
@param version_map map of { JavaOFVersion: OFClass (from loxi_ir) }
"""
self.c_name = c_name
self.version_map = version_map
# name: the Java Type name, e.g., OFFlowAdd
self.name = java_type.name_c_to_caps_camel(c_name) if c_name != "of_header" else "OFMessage"
# variable_name name to use for variables of this type. i.e., flowAdd
self.variable_name = self.name[2].lower() + self.name[3:]
self.title_name = self.variable_name[0].upper() + self.variable_name[1:]
# name for use in constants: FLOW_ADD
self.constant_name = c_name.upper().replace("OF_", "")
pck_suffix, parent_interface, self.type_annotation = self.class_info()
self.package = "org.projectfloodlight.openflow.protocol.%s" % pck_suffix if pck_suffix else "org.projectfloodlight.openflow.protocol"
if self.name != parent_interface:
self.parent_interface = parent_interface
else:
self.parent_interface = None
@property
@memoize
def all_parent_interfaces(self):
return [ "OFObject" ] + \
([ self.parent_interface ] if self.parent_interface else [] )+ \
self.additional_parent_interfaces
@property
@memoize
def additional_parent_interfaces(self):
if loxi_utils.class_is_message(self.c_name) and not self.is_virtual:
m = re.match(r'(.*)Request$', self.name)
if m:
reply_name = m.group(1) + "Reply"
if model.interface_by_name(reply_name):
return ["OFRequest<%s>" % reply_name ]
return []
def is_instance_of(self, other_class):
if self == other_class:
return True
parent = self.super_class
if parent is None:
return False
else:
return parent.is_instance_of(other_class)
@property
def super_class(self):
if not self.parent_interface:
return None
else:
return model.interface_by_name(self.parent_interface)
def inherited_declaration(self, type_spec="?"):
if self.type_annotation:
return "%s<%s>" % (self.name, type_spec)
else:
return "%s" % self.name
@property
def type_variable(self):
if self.type_annotation:
return "<T>"
else:
return "";
def class_info(self):
""" return tuple of (package_prefix, parent_class) for the current JavaOFInterface"""
# FIXME: This duplicates inheritance information that is now available in the loxi_ir
# model (note, that the loxi model is on versioned classes). Should check/infer the
# inheritance information from the versioned lox_ir classes.
if re.match(r'OFStatsRequest$', self.name):
return ("", "OFMessage", "T extends OFStatsReply")
elif re.match(r'OF.+StatsRequest$', self.name):
return ("", "OFStatsRequest<{}>".format(re.sub(r'Request$', 'Reply', self.name)), None)
elif re.match(r'OF.+StatsReply$', self.name):
return ("", "OFStatsReply", None)
elif re.match(r'OFFlow(Add|Modify(Strict)?|Delete(Strict)?)$', self.name):
return ("", "OFFlowMod", None)
elif loxi_utils.class_is_message(self.c_name) and re.match(r'OFBsn.+$', self.name) and self.name != "OFBsnHeader":
return ("", "OFBsnHeader", None)
elif loxi_utils.class_is_message(self.c_name) and re.match(r'OFNicira.+$', self.name) and self.name != "OFNiciraHeader":
return ("", "OFNiciraHeader", None)
elif self.name == "OFBsnHeader" or self.name =="OFNiciraHeader":
return ("", "OFExperimenter", None)
elif re.match(r'OFMatch.*', self.name):
return ("", "Match", None)
elif loxi_utils.class_is_message(self.c_name):
return ("", "OFMessage", None)
elif loxi_utils.class_is_action(self.c_name):
if re.match(r'OFActionBsn.+', self.name):
return ("action", "OFActionBsn", None)
elif re.match(r'OFActionNicira.+', self.name):
return ("action", "OFActionNicira", None)
elif self.name == "OFActionBsn" or self.name == "OFActionNicira":
return ("action", "OFActionExperimenter", None)
else:
return ("action", "OFAction", None)
elif re.match(r'OFBsnVport.+$', self.name):
return ("", "OFBsnVport", None)
elif self.name == "OFOxm":
return ("oxm", None, "T extends OFValueType<T>")
elif loxi_utils.class_is_oxm(self.c_name):
if self.name in model.oxm_map:
return ("oxm", "OFOxm<%s>" % model.oxm_map[self.name].type_name, None)
else:
return ("oxm", "OFOxm", None)
elif loxi_utils.class_is_instruction(self.c_name):
return ("instruction", "OFInstruction", None)
elif loxi_utils.class_is_meter_band(self.c_name):
return ("meterband", "OFMeterBand", None)
elif loxi_utils.class_is_queue_prop(self.c_name):
return ("queueprop", "OFQueueProp", None)
elif loxi_utils.class_is_hello_elem(self.c_name):
return ("", "OFHelloElem", None)
else:
return ("", None, None)
@property
@memoize
def writeable_members(self):
return [ m for m in self.members if m.is_writeable ]
@memoize
def member_by_name(self, name):
return find(lambda m: m.name == name, self.members)
@property
@memoize
def members(self):
return self.ir_model_members + self.virtual_members
@property
@memoize
def ir_model_members(self):
"""return a list of all members to be exposed by this interface. Corresponds to
the union of the members of the vesioned classes without length, fieldlength
and pads (those are handled automatically during (de)serialization and not exposed"""
all_versions = []
member_map = collections.OrderedDict()
for (version, of_class) in self.version_map.items():
for of_member in of_class.members:
if isinstance(of_member, OFLengthMember) or \
isinstance(of_member, OFFieldLengthMember) or \
isinstance(of_member, OFPadMember):
continue
if of_member.name not in member_map:
member_map[of_member.name] = JavaMember.for_of_member(self, of_member)
return tuple(m for m in member_map.values() if m.name not in model.read_blacklist[self.name])
@property
def virtual_members(self):
if self.name == "OFOxm":
return (
JavaVirtualMember(self, "value", java_type.generic_t),
JavaVirtualMember(self, "mask", java_type.generic_t),
JavaVirtualMember(self, "matchField", java_type.make_match_field_jtype("T")),
JavaVirtualMember(self, "masked", java_type.boolean),
)
elif self.parent_interface and self.parent_interface.startswith("OFOxm"):
field_type = java_type.make_match_field_jtype(model.oxm_map[self.name].type_name) \
if self.name in model.oxm_map \
else java_type.make_match_field_jtype()
return (
JavaVirtualMember(self, "matchField", field_type),
JavaVirtualMember(self, "masked", java_type.boolean),
) \
+ \
(
( JavaVirtualMember(self, "mask", find(lambda x: x.name == "value", self.ir_model_members).java_type), ) if not find(lambda x: x.name == "mask", self.ir_model_members) else
()
)
else:
return ()
@property
@memoize
def is_virtual(self):
""" Is this interface virtual. If so, do not generate a builder interface """
return self.name in model.virtual_interfaces or all(ir_class.virtual for ir_class in self.version_map.values())
@property
def is_universal(self):
""" Is this interface universal, i.e., does it exist in all OF versions? """
return len(self.all_versions) == len(model.versions)
@property
@memoize
def all_versions(self):
""" return list of all versions that this interface exists in """
return self.version_map.keys()
def has_version(self, version):
return version in self.version_map
def versioned_class(self, version):
return JavaOFClass(self, version, self.version_map[version])
@property
@memoize
def versioned_classes(self):
return [ self.versioned_class(version) for version in self.all_versions ]
#######################################################################
### (Versioned) Classes
#######################################################################
class JavaOFClass(object):
""" Models an OpenFlow Message class for the purpose of the java class.
Version specific child of a JavaOFInterface
"""
def __init__(self, interface, version, ir_class):
"""
@param interface JavaOFInterface instance of the parent interface
@param version JavaOFVersion
@param ir_class OFClass from loxi_ir
"""
self.interface = interface
self.ir_class = ir_class
self.c_name = self.ir_class.name
self.version = version
self.constant_name = self.c_name.upper().replace("OF_", "")
self.package = "org.projectfloodlight.openflow.protocol.ver%s" % version.of_version
self.generated = False
@property
@memoize
def unit_test(self):
return JavaUnitTestSet(self)
@property
def name(self):
return "%sVer%s" % (self.interface.name, self.version.of_version)
@property
def variable_name(self):
return self.name[3:]
@property
def length(self):
if self.is_fixed_length:
return self.min_length
else:
raise Exception("No fixed length for class %s, version %s" % (self.name, self.version))
@property
def min_length(self):
""" @return the minimum wire length of an instance of this class in bytes """
id_tuple = (self.ir_class.name, self.version.int_version)
return of_g.base_length[id_tuple] if id_tuple in of_g.base_length else -1
@property
def is_fixed_length(self):
""" true iff this class serializes to a fixed length on the wire """
return (self.ir_class.name, self.version.int_version) in of_g.is_fixed_length and \
not self.is_virtual
def all_properties(self):
return self.interface.members
def get_member(self, name):
for m in self.members:
if m.name == name:
return m
@property
@memoize
def data_members(self):
return [ prop for prop in self.members if prop.is_data ]
@property
@memoize
def fixed_value_members(self):
return [ prop for prop in self.members if prop.is_fixed_value ]
@property
@memoize
def public_members(self):
return [ prop for prop in self.members if prop.is_public ]
@property
@memoize
def members(self):
return self.ir_model_members + self.virtual_members
@property
def ir_model_members(self):
members = [ JavaMember.for_of_member(self, of_member) for of_member in self.ir_class.members ]
return tuple(members)
@property
def virtual_members(self):
if self.interface.parent_interface and self.interface.parent_interface.startswith("OFOxm"):
if self.interface.name in model.oxm_map:
oxm_entry = model.oxm_map[self.interface.name]
return (
JavaVirtualMember(self, "matchField", java_type.make_match_field_jtype(oxm_entry.type_name), "MatchField.%s" % oxm_entry.value),
JavaVirtualMember(self, "masked", java_type.boolean, "true" if oxm_entry.masked else "false"),
)
else:
return (
JavaVirtualMember(self, "matchField", java_type.make_match_field_jtype(), "null"),
JavaVirtualMember(self, "masked", java_type.boolean, "false"),
)
else:
return ()
def all_versions(self):
return [ JavaOFVersion(int_version)
for int_version in of_g.unified[self.c_name]
if int_version != 'union' and int_version != 'object_id' ]
def version_is_inherited(self, version):
return 'use_version' in of_g.unified[self.ir_class.name][version.int_version]
def inherited_from(self, version):
return JavaOFVersion(of_g.unified[self.ir_class.name][version.int_version]['use_version'])
@property
def is_virtual(self):
return self.ir_class.virtual # type_maps.class_is_virtual(self.c_name) or self.ir_class.virtual
@property
def discriminator(self):
return find(lambda m: isinstance(m, OFDiscriminatorMember), self.ir_class.members)
@property
def is_extension(self):
return type_maps.message_is_extension(self.c_name, -1)
@property
def align(self):
return int(self.ir_class.params['align']) if 'align' in self.ir_class.params else 0
@property
@memoize
def superclass(self):
return find(lambda c: c.version == self.version and c.c_name == self.ir_class.superclass, model.all_classes)
@property
@memoize
def subclasses(self):
return [ c for c in model.all_classes if c.version == self.version and c.ir_class.superclass == self.c_name ]
#######################################################################
### Member
#######################################################################
class JavaMember(object):
""" Models a property (member) of an openflow class. """
def __init__(self, msg, name, java_type, member):
self.msg = msg
self.name = name
self.java_type = java_type
self.member = member
self.c_name = self.member.name if(hasattr(self.member, "name")) else ""
@property
def title_name(self):
return self.name[0].upper() + self.name[1:]
@property
def constant_name(self):
return self.c_name.upper()
@property
def getter_name(self):
return ("is" if self.java_type.public_type == "boolean" else "get") + self.title_name
@property
def setter_name(self):
return "set" + self.title_name
@property
def default_name(self):
if self.is_fixed_value:
return self.constant_name
else:
return "DEFAULT_"+self.constant_name
@property
def default_value(self):
if self.is_fixed_value:
return self.enum_value
else:
default = self.java_type.default_op(self.msg.version)
if default == "null" and not self.is_nullable:
return None
else:
return default
@property
def enum_value(self):
if self.name == "version":
return "OFVersion.%s" % self.msg.version.constant_version
java_type = self.java_type.public_type;
try:
global model
enum = model.enum_by_name(java_type)
entry = enum.entry_by_version_value(self.msg.version, self.value)
return "%s.%s" % ( enum.name, entry.name)
except KeyError, e:
print e.message
return self.value
@property
def is_pad(self):
return isinstance(self.member, OFPadMember)
def is_type_value(self, version=None):
if(version==None):
return any(self.is_type_value(version) for version in self.msg.all_versions)
try:
return self.c_name in get_type_values(self.msg.c_name, version.int_version)
except:
return False
@property
def is_field_length_value(self):
return isinstance(self.member, OFFieldLengthMember)
@property
def is_discriminator(self):
return isinstance(self.member, OFDiscriminatorMember)
@property
def is_length_value(self):
return isinstance(self.member, OFLengthMember)
@property
def is_public(self):
return not (self.is_pad or self.is_length_value)
@property
def is_data(self):
return isinstance(self.member, OFDataMember) and self.name != "version"
@property
def is_fixed_value(self):
return hasattr(self.member, "value") or self.name == "version" \
or ( self.name == "length" and self.msg.is_fixed_length) \
or ( self.name == "len" and self.msg.is_fixed_length)
@property
def value(self):
if self.name == "version":
return self.msg.version.int_version
elif self.name == "length" or self.name == "len":
return self.msg.length
else:
return self.java_type.format_value(self.member.value)
@property
def priv_value(self):
if self.name == "version":
return self.msg.version.int_version
elif self.name == "length" or self.name == "len":
return self.msg.length
else:
return self.java_type.format_value(self.member.value, pub_type=False)
@property
def is_writeable(self):
return self.is_data and not self.name in model.write_blacklist[self.msg.name]
def get_type_value_info(self, version):
return get_type_values(msg.c_name, version.int_version)[self.c_name]
@property
def length(self):
if hasattr(self.member, "length"):
return self.member.length
else:
count, base = loxi_utils.type_dec_to_count_base(self.member.type)
return of_g.of_base_types[base]['bytes'] * count
@staticmethod
def for_of_member(java_class, member):
if isinstance(member, OFPadMember):
return JavaMember(None, "", None, member)
else:
if member.name == 'len':
name = 'length'
elif member.name == 'value_mask':
name = 'mask'
else:
name = java_type.name_c_to_camel(member.name)
j_type = java_type.convert_to_jtype(java_class.c_name, member.name, member.oftype)
return JavaMember(java_class, name, j_type, member)
@property
def is_universal(self):
if not self.msg.c_name in of_g.unified:
print("%s not self.unified" % self.msg.c_name)
return False
for version in of_g.unified[self.msg.c_name]:
if version == 'union' or version =='object_id':
continue
if 'use_version' in of_g.unified[self.msg.c_name][version]:
continue
if not self.member.name in (f['name'] for f in of_g.unified[self.msg.c_name][version]['members']):
return False
return True
@property
def is_virtual(self):
return False
def __hash__(self):
return hash(self.name)
def __eq__(self, other):
if other is None or type(self) != type(other):
return False
return (self.name,) == (other.name,)
@property
def is_nullable(self):
return self.name in model.nullable_map[self.msg.name]
class JavaVirtualMember(JavaMember):
""" Models a virtual property (member) of an openflow class that is not backed by a loxi ir member """
def __init__(self, msg, name, java_type, value=None):
JavaMember.__init__(self, msg, name, java_type, member=None)
self._value = value
@property
def is_fixed_value(self):
return True
@property
def value(self):
return self._value
@property
def priv_value(self):
return self._value
@property
def is_universal(self):
return True
@property
def is_virtual(self):
return True
#######################################################################
### Unit Test
#######################################################################
class JavaUnitTestSet(object):
def __init__(self, java_class):
self.java_class = java_class
first_data_file_name = "of{version}/{name}.data".format(version=java_class.version.of_version,
name=java_class.c_name[3:])
data_file_template = "of{version}/{name}.".format(version=java_class.version.of_version,
name=java_class.c_name[3:]) + "{i}.data"
test_class_name = self.java_class.name + "Test"
self.test_units = []
if test_data.exists(first_data_file_name):
self.test_units.append(JavaUnitTest(java_class, first_data_file_name, test_class_name))
i = 1
while test_data.exists(data_file_template.format(i=i)):
self.test_units.append(JavaUnitTest(java_class, data_file_template.format(i=i), test_class_name + str(i)))
i = i + 1
@property
def package(self):
return self.java_class.package
@property
def has_test_data(self):
return len(self.test_units) > 0
@property
def length(self):
return len(self.test_units)
def get_test_unit(self, i):
return self.test_units[i]
class JavaUnitTest(object):
def __init__(self, java_class, file_name=None, test_class_name=None):
self.java_class = java_class
if file_name is None:
self.data_file_name = "of{version}/{name}.data".format(version=java_class.version.of_version,
name=java_class.c_name[3:])
else:
self.data_file_name = file_name
if test_class_name is None:
self.test_class_name = self.java_class.name + "Test"
else:
self.test_class_name = test_class_name
@property
def package(self):
return self.java_class.package
@property
def name(self):
return self.test_class_name
@property
def has_test_data(self):
return test_data.exists(self.data_file_name)
@property
@memoize
def test_data(self):
return test_data.read(self.data_file_name)
#######################################################################
### Enums
#######################################################################
class JavaEnum(object):
def __init__(self, c_name, version_enum_map):
self.c_name = c_name
if c_name == "of_stats_types":
self.name = "OFStatsType"
else:
self.name = "OF" + java_type.name_c_to_caps_camel("_".join(c_name.split("_")[1:]))
# Port_features has constants that start with digits
self.name_prefix = "PF_" if self.name == "OFPortFeatures" else ""
self.version_enums = version_enum_map
entry_name_version_value_map = OrderedDefaultDict(lambda: OrderedDict())
for version, ir_enum in version_enum_map.items():
for ir_entry in ir_enum.entries:
entry_name_version_value_map[ir_entry.name][version] = ir_entry.value
self.entries = [ JavaEnumEntry(self, name, version_value_map)
for (name, version_value_map) in entry_name_version_value_map.items() ]
self.entries = [ e for e in self.entries if e.name not in model.enum_entry_blacklist[self.name] ]
self.package = "org.projectfloodlight.openflow.protocol"
self.metadata = model.enum_metadata_map[self.name]
def wire_type(self, version):
ir_enum = self.version_enums[version]
if "wire_type" in ir_enum.params:
return java_type.convert_enum_wire_type_to_jtype(ir_enum.params["wire_type"])
else:
return java_type.u8
@property
@memoize
def is_bitmask(self):
return any(ir_enum.is_bitmask for ir_enum in self.version_enums.values())
@property
def versions(self):
return self.version_enums.keys()
@memoize
def entry_by_name(self, name):
res = find(lambda e: e.name == name, self.entries)
if res:
return res
else:
raise KeyError("Enum %s: no entry with name %s" % (self.name, name))
@memoize
def entry_by_c_name(self, name):
res = find(lambda e: e.c_name == name, self.entries)
if res:
return res
else:
raise KeyError("Enum %s: no entry with c_name %s" % (self.name, name))
@memoize
def entry_by_version_value(self, version, value):
res = find(lambda e: e.values[version] == value if version in e.values else False, self.entries)
if res:
return res
else:
raise KeyError("Enum %s: no entry with version %s, value %s" % (self.name, version, value))
# values: Map JavaVersion->Value
class JavaEnumEntry(object):
def __init__(self, enum, name, values):
self.enum = enum
self.name = enum.name_prefix + "_".join(name.split("_")[1:]).upper()
self.values = values
@property
def constructor_params(self):
return [ m.value(self) for m in self.enum.metadata.properties ]
def has_value(self, version):
return version in self.values
def value(self, version):
return self.values[version]
def format_value(self, version):
res = self.enum.wire_type(version).format_value(self.values[version])
return res
def all_values(self, versions, not_present=None):
return [ self.values[version] if version in self.values else not_present for version in versions ]
@property
@memoize
def masked_enum_group(self):
group = find(lambda g: self.name in g.members, model.masked_enum_groups[self.enum.name])
return group
@property
@memoize
def is_mask(self):
return any(self.name == g.mask for g in model.masked_enum_groups[self.enum.name])