blob: 059c3631056385cd58081716930d516c610074c5 [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.
"""
@brief Utilities involving LOXI naming conventions
Utility functions for OpenFlow class generation
These may need to be sorted out into language specific functions
"""
import sys
import os
import of_g
import tenjin
from generic_utils import find, memoize
def class_signature(members):
"""
Generate a signature string for a class in canonical form
@param cls The class whose signature is to be generated
"""
return ";".join([",".join([x["m_type"], x["name"], str(x["offset"])])
for x in members])
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 of_g.ofp_constants:
count = of_g.ofp_constants[count_str]
else:
count = int(count_str)
base_type = chk_ar[0]
else:
base_type = m_type
return count, base_type
##
# Class types:
#
# Virtual
# A virtual class is one which does not have an explicit wire
# representation. For example, an inheritance super class
# or a list type.
#
# List
# A list of objects of some other type
#
# TLV16
# The wire represenation starts with 16-bit type and length fields
#
# OXM
# An extensible match object
#
# Message
# A top level OpenFlow message
#
#
def class_is_message(cls):
"""
Return True if cls is a message object based on info in unified
"""
return "xid" in of_g.unified[cls]["union"] and cls != "of_header"
def class_is_tlv16(cls):
"""
Return True if cls_name is an object which uses uint16 for type and length
"""
if cls.find("of_action") == 0: # Includes of_action_id classes
return True
if cls.find("of_instruction") == 0:
return True
if cls.find("of_queue_prop") == 0:
return True
if cls.find("of_table_feature_prop") == 0:
return True
# *sigh*
if cls.find("of_meter_band_stats") == 0: # NOT A TLV
return False
if cls.find("of_meter_band") == 0:
return True
if cls.find("of_hello_elem") == 0:
return True
if cls == "of_match_v3":
return True
if cls == "of_match_v4":
return True
return False
def class_is_u16_len(cls):
"""
Return True if cls_name is an object which uses initial uint16 length
"""
return cls in ["of_group_desc_stats_entry", "of_group_stats_entry",
"of_flow_stats_entry", "of_bucket", "of_table_features"]
def class_is_oxm(cls):
"""
Return True if cls_name is an OXM object
"""
if cls.find("of_oxm") == 0:
return True
return False
def class_is_action(cls):
"""
Return True if cls_name is an action object
Note that action_id is not an action object, though it has
the same header. It looks like an action header, but the type
is used to identify a kind of action, it does not indicate the
type of the object following.
"""
if cls.find("of_action_id") == 0:
return False
if cls.find("of_action") == 0:
return True
# For each vendor, check for vendor specific action
for exp in of_g.experimenter_name_to_id:
if cls.find("of_action" + exp) == 0:
return True
return False
def class_is_action_id(cls):
"""
Return True if cls_name is an action object
Note that action_id is not an action object, though it has
the same header. It looks like an action header, but the type
is used to identify a kind of action, it does not indicate the
type of the object following.
"""
if cls.find("of_action_id") == 0:
return True
# For each vendor, check for vendor specific action
for exp in of_g.experimenter_name_to_id:
if cls.find("of_action_id_" + exp) == 0:
return True
return False
def class_is_instruction(cls):
"""
Return True if cls_name is an instruction object
"""
if cls.find("of_instruction") == 0:
return True
# For each vendor, check for vendor specific action
for exp in of_g.experimenter_name_to_id:
if cls.find("of_instruction_" + exp) == 0:
return True
return False
def class_is_meter_band(cls):
"""
Return True if cls_name is an instruction object
"""
# meter_band_stats is not a member of meter_band class hierarchy
if cls.find("of_meter_band_stats") == 0:
return False
if cls.find("of_meter_band") == 0:
return True
return False
def class_is_hello_elem(cls):
"""
Return True if cls_name is an instruction object
"""
if cls.find("of_hello_elem") == 0:
return True
return False
def class_is_queue_prop(cls):
"""
Return True if cls_name is a queue_prop object
"""
if cls.find("of_queue_prop") == 0:
return True
# For each vendor, check for vendor specific action
for exp in of_g.experimenter_name_to_id:
if cls.find("of_queue_prop_" + exp) == 0:
return True
return False
def class_is_table_feature_prop(cls):
"""
Return True if cls_name is a queue_prop object
"""
if cls.find("of_table_feature_prop") == 0:
return True
return False
def class_is_stats_message(cls):
"""
Return True if cls_name is a message object based on info in unified
"""
return "stats_type" in of_g.unified[cls]["union"]
def class_is_list(cls):
"""
Return True if cls_name is a list object
"""
return (cls.find("of_list_") == 0)
def type_is_of_object(m_type):
"""
Return True if m_type is an OF object type
"""
# Remove _t from the type id and see if key for unified class
if m_type[-2:] == "_t":
m_type = m_type[:-2]
return m_type in of_g.unified
def list_to_entry_type(cls):
"""
Return the entry type for a list
"""
slen = len("of_list_")
return "of_" + cls[slen:]
def type_to_short_name(m_type):
if m_type in of_g.of_base_types:
tname = of_g.of_base_types[m_type]["short_name"]
elif m_type in of_g.of_mixed_types:
tname = of_g.of_mixed_types[m_type]["short_name"]
else:
tname = "unknown"
return tname
def type_to_name_type(cls, member_name):
"""
Generate the root name of a member for accessor functions, etc
@param cls The class name
@param member_name The member name
"""
members = of_g.unified[cls]["union"]
if not member_name in members:
debug("Error: %s is not in class %s for acc_name defn" %
(member_name, cls))
os.exit()
mem = members[member_name]
m_type = mem["m_type"]
id = mem["memid"]
tname = type_to_short_name(m_type)
return "o%d_m%d_%s" % (of_g.unified[cls]["object_id"], id, tname)
def member_to_index(m_name, members):
"""
Given a member name, return the index in the members dict
@param m_name The name of the data member to search for
@param members The dict of members
@return Index if found, -1 not found
Note we could generate an index when processing the original input
"""
count = 0
for d in members:
if d["name"] == m_name:
return count
count += 1
return -1
def member_base_type(cls, m_name):
"""
Map a member to its of_ type
@param cls The class name
@param m_name The name of the member being gotten
@return The of_ type of the member
"""
rv = of_g.unified[cls]["union"][m_name]["m_type"]
if rv[-2:] == "_t":
return rv
return rv + "_t"
def member_type_is_octets(cls, m_name):
return member_base_type(cls, m_name) == "of_octets_t"
def member_returns_val(cls, m_name):
"""
Should get accessor return a value rather than void
@param cls The class name
@param m_name The member name
@return True if of_g config and the specific member allow a
return value. Otherwise False
"""
m_type = of_g.unified[cls]["union"][m_name]["m_type"]
return (config_check("get_returns") =="value" and
m_type in of_g.of_scalar_types)
def config_check(str, dictionary = of_g.code_gen_config):
"""
Return config value if in dictionary; else return False.
@param str The lookup index
@param dictionary The dict to check; use code_gen_config if None
"""
if str in dictionary:
return dictionary[str]
return False
def h_file_to_define(name):
"""
Convert a .h file name to the define used for the header
"""
h_name = name[:-2].upper()
h_name = "_" + h_name + "_H_"
return h_name
def type_to_cof_type(m_type):
if m_type in of_g.of_base_types:
if "cof_type" in of_g.of_base_types[m_type]:
return of_g.of_base_types[m_type]["cof_type"]
return m_type
def member_is_scalar(cls, m_name):
return of_g.unified[cls]["union"][m_name]["m_type"] in of_g.of_scalar_types
def type_is_scalar(m_type):
return m_type in of_g.of_scalar_types
def skip_member_name(name):
return name.find("pad") == 0 or name in of_g.skip_members
def enum_name(cls):
"""
Return the name used for an enum identifier for the given class
@param cls The class name
"""
return cls.upper()
def class_in_version(cls, ver):
"""
Return boolean indicating if cls is defined for wire version ver
"""
return (cls, ver) in of_g.base_length
def instance_to_class(instance, parent):
"""
Return the name of the class for an instance of inheritance type parent
"""
return parent + "_" + instance
def sub_class_to_var_name(cls):
"""
Given a subclass name like of_action_output, generate the
name of a variable like 'output'
@param cls The class name
"""
pass
def class_is_var_len(cls, version):
# Match is special case. Only version 1.2 (wire version 3) is var
if cls == "of_match":
return version == 3
return not (cls, version) in of_g.is_fixed_length
def base_type_to_length(base_type, version):
if base_type + "_t" in of_g.of_base_types:
inst_len = of_g.of_base_types[base_type + "_t"]["bytes"]
else:
inst_len = of_g.base_length[(base_type, version)]
def version_to_name(version):
"""
Convert an integer version to the C macro name
"""
return "OF_" + of_g.version_names[version]
##
# Is class a flow modify of some sort?
def cls_is_flow_mod(cls):
return cls in ["of_flow_mod", "of_flow_modify", "of_flow_add", "of_flow_delete",
"of_flow_modify_strict", "of_flow_delete_strict"]
def all_member_types_get(cls, version):
"""
Get the members and list of types for members of a given class
@param cls The class name to process
@param version The version for the class
"""
member_types = []
if not version in of_g.unified[cls]:
return ([], [])
if "use_version" in of_g.unified[cls][version]:
v = of_g.unified[cls][version]["use_version"]
members = of_g.unified[cls][v]["members"]
else:
members = of_g.unified[cls][version]["members"]
# Accumulate variables that are supported
for member in members:
m_type = member["m_type"]
m_name = member["name"]
if skip_member_name(m_name):
continue
if not m_type in member_types:
member_types.append(m_type)
return (members, member_types)
def list_name_extract(list_type):
"""
Return the base name for a list object of the given type
@param list_type The type of the list as appears in the input,
for example list(of_port_desc_t).
@return A pair, (list-name, base-type) where list-name is the
base name for the list, for example of_list_port_desc, and base-type
is the type of list elements like of_port_desc_t
"""
base_type = list_type[5:-1]
list_name = base_type
if list_name.find("of_") == 0:
list_name = list_name[3:]
if list_name[-2:] == "_t":
list_name = list_name[:-2]
list_name = "of_list_" + list_name
return (list_name, base_type)
def version_to_name(version):
"""
Convert an integer version to the C macro name
"""
return "OF_" + of_g.version_names[version]
def gen_c_copy_license(out):
"""
Generate the top comments for copyright and license
"""
import c_gen.util
c_gen.util.render_template(out, '_copyright.c')
def accessor_returns_error(a_type, m_type):
is_var_len = (not type_is_scalar(m_type)) and \
[x for x in of_g.of_version_range if class_is_var_len(m_type[:-2], x)] != []
if a_type == "set" and is_var_len:
return True
elif m_type == "of_match_t":
return True
else:
return False
def render_template(out, name, path, context, prefix = None):
"""
Render a template using tenjin.
out: a file-like object
name: name of the template
path: array of directories to search for the template
context: dictionary of variables to pass to the template
prefix: optional prefix to use for embedding (for other languages than python)
"""
pp = [ tenjin.PrefixedLinePreprocessor(prefix=prefix) if prefix else tenjin.PrefixedLinePreprocessor() ] # support "::" syntax
template_globals = { "to_str": str, "escape": str } # disable HTML escaping
engine = TemplateEngine(path=path, pp=pp)
out.write(engine.render(name, context, template_globals))
def render_static(out, name, path):
"""
Write out a static template.
out: a file-like object
name: name of the template
path: array of directories to search for the template
"""
# Reuse the tenjin logic for finding the template
template_filename = tenjin.FileSystemLoader().find(name, path)
if not template_filename:
raise ValueError("template %s not found" % name)
with open(template_filename) as infile:
out.write(infile.read())
@memoize
def lookup_ir_wiretype(oftype, version):
""" if of is a reference to an enum in ir, resolve it to the wiretype
declared in that enum. Else return oftype """
enums = of_g.ir[version].enums
enum = find(lambda e: e.name == oftype, enums)
if enum and 'wire_type' in enum.params:
return enum.params['wire_type']
else:
return oftype
class TemplateEngine(tenjin.Engine):
def include(self, template_name, **kwargs):
"""
Tenjin has an issue with nested includes that use the same local variable
names, because it uses the same context dict for each level of nesting.
The fix is to copy the context.
"""
frame = sys._getframe(1)
locals = frame.f_locals
globals = frame.f_globals
context = locals["_context"].copy()
context.update(kwargs)
template = self.get_template(template_name, context, globals)
return template.render(context, globals, _buf=locals["_buf"])
def open_output(name):
"""
Open an output file for writing
'name' may include slashes. Subdirectories will be automatically created.
"""
print "Writing %s" % name
path = os.path.join(of_g.options.install_dir, name)
dirpath = os.path.dirname(path)
if not os.path.exists(dirpath):
os.makedirs(dirpath)
return open(path, "w")