Merge branch 'master' of github.com:floodlight/loxigen into group_mod
Conflicts:
java_gen/java_model.py
loxi_front_end/frontend.py
loxi_front_end/frontend_ir.py
loxigen.py
py_gen/templates/message.py
utest/test_frontend.py
diff --git a/Makefile b/Makefile
index fe5f19e..4712a04 100644
--- a/Makefile
+++ b/Makefile
@@ -106,10 +106,7 @@
check-all: check check-c check-py check-java
check:
- ./utest/test_parser.py
- ./utest/test_frontend.py
- ./utest/test_test_data.py
- ./utest/test_generic_utils.py
+ nosetests
check-py: python
PYTHONPATH=${LOXI_OUTPUT_DIR}/pyloxi:. python py_gen/tests/generic_util.py
@@ -134,8 +131,10 @@
install-java: java
cd ${OPENFLOWJ_WORKSPACE} && mvn install
-
pylint:
pylint -E ${LOXI_PY_FILES}
+ctags:
+ ctags ${LOXI_PY_FILES} ${LOXI_TEMPLATE_FILES} ${INPUT_FILES} ${TEST_DATA}
+
.PHONY: all clean debug check pylint c python
diff --git a/c_gen/build_of_g.py b/c_gen/build_of_g.py
new file mode 100755
index 0000000..9ea6d34
--- /dev/null
+++ b/c_gen/build_of_g.py
@@ -0,0 +1,559 @@
+# 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 re
+import string
+import os
+import glob
+import copy
+import collections
+import c_gen.of_g_legacy as of_g
+import c_gen.type_maps as type_maps
+import c_gen.loxi_utils_legacy as loxi_utils
+import loxi_globals
+import c_gen.identifiers as identifiers
+import pyparsing
+import loxi_front_end.parser as parser
+import c_gen.translation as translation
+import loxi_front_end.frontend as frontend
+from loxi_ir import *
+from generic_utils import *
+
+root_dir = os.path.dirname(os.path.realpath(__file__))
+
+versions = {}
+# TODO: Put these in a class so they get documented
+
+## Dict indexed by version giving all info related to version
+#
+# This is local; after processing, the information is stored in
+# of_g variables.
+
+def add_class(wire_version, cls, members):
+ """
+ Process a class for the given version and update the unified
+ list of classes as needed.
+
+ @param wire_version The wire version for this class defn
+ @param cls The name of the class being added
+ @param members The list of members with offsets calculated
+ """
+ memid = 0
+
+ sig = loxi_utils.class_signature(members)
+ if cls in of_g.unified:
+ uc = of_g.unified[cls]
+ if wire_version in uc:
+ debug("Error adding %s to unified. Wire ver %d exists" %
+ (cls, wire_version))
+ sys.exit(1)
+ uc[wire_version] = {}
+ # Check for a matching signature
+ for wver in uc:
+ if type(wver) != type(0): continue
+ if wver == wire_version: continue
+ if not "use_version" in uc[wver]:
+ if sig == loxi_utils.class_signature(uc[wver]["members"]):
+ log("Matched %s, ver %d to ver %d" %
+ (cls, wire_version, wver))
+ # have a match with existing version
+ uc[wire_version]["use_version"] = wver
+ # What else to do?
+ return
+ else: # Haven't seen this entry before
+ log("Adding %s to unified list, ver %d" % (cls, wire_version))
+ of_g.unified[cls] = dict(union={})
+ uc = of_g.unified[cls]
+
+ # At this point, need to add members for this version
+ uc[wire_version] = dict(members = members)
+
+ # Per member processing:
+ # Add to union list (I'm sure there's a better way)
+ # Check if it's a list
+ union = uc["union"]
+ if not cls in of_g.ordered_members:
+ of_g.ordered_members[cls] = []
+ for member in members:
+ m_name = member["name"]
+ m_type = member["m_type"]
+ if m_name.find("pad") == 0:
+ continue
+ if m_name in union:
+ if not m_type == union[m_name]["m_type"]:
+ debug("ERROR: CLASS: %s. VERSION %d. MEMBER: %s. TYPE: %s" %
+ (cls, wire_version, m_name, m_type))
+ debug(" Type conflict adding member to unified set.")
+ debug(" Current union[%s]:" % m_name)
+ debug(union[m_name])
+ sys.exit(1)
+ else:
+ union[m_name] = dict(m_type=m_type, memid=memid)
+ memid += 1
+ if not m_name in of_g.ordered_members[cls]:
+ of_g.ordered_members[cls].append(m_name)
+
+def update_offset(cls, wire_version, name, offset, m_type):
+ """
+ Update (and return) the offset based on type.
+ @param cls The parent class
+ @param wire_version The wire version being processed
+ @param name The name of the data member
+ @param offset The current offset
+ @param m_type The type declaration being processed
+ @returns A pair (next_offset, len_update) next_offset is the new offset
+ of the next object or -1 if this is a var-length object. len_update
+ is the increment that should be added to the length. Note that (for
+ of_match_v3) it is variable length, but it adds 8 bytes to the fixed
+ length of the object
+ If offset is already -1, do not update
+ Otherwise map to base type and count and update (if possible)
+ """
+ if offset < 0: # Don't update offset once set to -1
+ return offset, 0
+
+ count, base_type = loxi_utils.type_dec_to_count_base(m_type)
+
+ len_update = 0
+ if base_type in of_g.of_mixed_types:
+ base_type = of_g.of_mixed_types[base_type][wire_version]
+
+ base_class = base_type[:-2]
+ if (base_class, wire_version) in of_g.is_fixed_length:
+ bytes = of_g.base_length[(base_class, wire_version)]
+ else:
+ if base_type == "of_match_v3_t":
+ # This is a special case: it has non-zero min length
+ # but is variable length
+ bytes = -1
+ len_update = 8
+ elif base_type in of_g.of_base_types:
+ bytes = of_g.of_base_types[base_type]["bytes"]
+ else:
+ print "UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type)
+ log("UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type))
+ bytes = -1
+
+ # If bytes
+ if bytes > 0:
+ len_update = count * bytes
+
+ if bytes == -1:
+ return -1, len_update
+
+ return offset + (count * bytes), len_update
+
+def calculate_offsets_and_lengths(ordered_classes, classes, wire_version):
+ """
+ Generate the offsets for fixed offset class members
+ Also calculate the class_sizes when possible.
+
+ @param classes The classes to process
+ @param wire_version The wire version for this set of classes
+
+ Updates global variables
+ """
+
+ lists = set()
+
+ # Generate offsets
+ for cls in ordered_classes:
+ fixed_offset = 0 # The last "good" offset seen
+ offset = 0
+ last_offset = 0
+ last_name = "-"
+ for member in classes[cls]:
+ m_type = member["m_type"]
+ name = member["name"]
+ if last_offset == -1:
+ if name == "pad":
+ log("Skipping pad for special offset for %s" % cls)
+ else:
+ log("SPECIAL OFS: Member %s (prev %s), class %s ver %d" %
+ (name, last_name, cls, wire_version))
+ if (((cls, name) in of_g.special_offsets) and
+ (of_g.special_offsets[(cls, name)] != last_name)):
+ debug("ERROR: special offset prev name changed")
+ debug(" cls %s. name %s. version %d. was %s. now %s" %
+ cls, name, wire_version,
+ of_g.special_offsets[(cls, name)], last_name)
+ sys.exit(1)
+ of_g.special_offsets[(cls, name)] = last_name
+
+ member["offset"] = offset
+ if m_type.find("list(") == 0:
+ (list_name, base_type) = loxi_utils.list_name_extract(m_type)
+ lists.add(list_name)
+ member["m_type"] = list_name + "_t"
+ offset = -1
+ elif m_type.find("struct") == 0:
+ debug("ERROR found struct: %s.%s " % (cls, name))
+ sys.exit(1)
+ elif m_type == "octets":
+ log("offset gen skipping octets: %s.%s " % (cls, name))
+ offset = -1
+ else:
+ offset, len_update = update_offset(cls, wire_version, name,
+ offset, m_type)
+ if offset != -1:
+ fixed_offset = offset
+ else:
+ fixed_offset += len_update
+ log("offset is -1 for %s.%s version %d " %
+ (cls, name, wire_version))
+ last_offset = offset
+ last_name = name
+ of_g.base_length[(cls, wire_version)] = fixed_offset
+ if (offset != -1):
+ of_g.is_fixed_length.add((cls, wire_version))
+ for list_type in lists:
+ classes[list_type] = []
+ of_g.ordered_classes[wire_version].append(list_type)
+ of_g.base_length[(list_type, wire_version)] = 0
+
+def order_and_assign_object_ids():
+ """
+ Order all classes and assign object ids to all classes.
+
+ This is done to promote a reasonable order of the objects, putting
+ messages first followed by non-messages. No assumptions should be
+ made about the order, nor about contiguous numbering. However, the
+ numbers should all be reasonably small allowing arrays indexed by
+ these enum values to be defined.
+ """
+
+ # Generate separate message and non-message ordered lists
+ for cls in of_g.unified:
+ if loxi_utils.class_is_message(cls):
+ of_g.ordered_messages.append(cls)
+ elif loxi_utils.class_is_list(cls):
+ of_g.ordered_list_objects.append(cls)
+ else:
+ of_g.ordered_non_messages.append(cls)
+
+ of_g.ordered_messages.sort()
+ of_g.ordered_pseudo_objects.sort()
+ of_g.ordered_non_messages.sort()
+ of_g.ordered_list_objects.sort()
+ of_g.standard_class_order.extend(of_g.ordered_messages)
+ of_g.standard_class_order.extend(of_g.ordered_non_messages)
+ of_g.standard_class_order.extend(of_g.ordered_list_objects)
+
+ # This includes pseudo classes for which most code is not generated
+ of_g.all_class_order.extend(of_g.ordered_messages)
+ of_g.all_class_order.extend(of_g.ordered_non_messages)
+ of_g.all_class_order.extend(of_g.ordered_list_objects)
+ of_g.all_class_order.extend(of_g.ordered_pseudo_objects)
+
+ # Assign object IDs
+ for cls in of_g.ordered_messages:
+ of_g.unified[cls]["object_id"] = of_g.object_id
+ of_g.object_id += 1
+ for cls in of_g.ordered_non_messages:
+ of_g.unified[cls]["object_id"] = of_g.object_id
+ of_g.object_id += 1
+ for cls in of_g.ordered_list_objects:
+ of_g.unified[cls]["object_id"] = of_g.object_id
+ of_g.object_id += 1
+ for cls in of_g.ordered_pseudo_objects:
+ of_g.unified[cls] = {}
+ of_g.unified[cls]["object_id"] = of_g.object_id
+ of_g.object_id += 1
+
+
+def initialize_versions():
+ """
+ Create an empty datastructure for each target version.
+ """
+
+ for version in loxi_globals.OFVersions.target_versions:
+ wire_version = version.wire_version
+ version_name = of_g.of_version_wire2name[wire_version]
+ of_g.wire_ver_map[wire_version] = version_name
+ versions[version_name] = dict(
+ version_name = version_name,
+ wire_version = wire_version,
+ classes = {})
+ of_g.ordered_classes[wire_version] = []
+
+ of_g.target_version_list = [ v.wire_version for v in loxi_globals.OFVersions.target_versions ]
+
+def build_ordered_classes():
+ """
+ Read in from files given on command line and update global state
+
+ @fixme Should select versions to support from command line
+ """
+
+ for version, protocol in loxi_globals.ir.items():
+ wire_version = version.wire_version
+ # Populate global state
+ version_name = of_g.of_version_wire2name[wire_version]
+
+ for ofclass in protocol.classes:
+ if ofclass.name in ("of_group_add", "of_group_modify", "of_group_delete"):
+ continue
+ of_g.ordered_classes[wire_version].append(ofclass.name)
+ legacy_members = []
+ pad_count = 0
+ for m in ofclass.members:
+ if type(m) == OFPadMember:
+ m_name = 'pad%d' % pad_count
+ if m_name == 'pad0': m_name = 'pad'
+ legacy_members.append(dict(m_type='uint8_t[%d]' % m.length,
+ name=m_name))
+ pad_count += 1
+ else:
+ # HACK the C backend does not yet support of_oxm_t
+ if m.oftype == 'of_oxm_t':
+ m_type = 'of_octets_t'
+ else:
+ enum = find(lambda e: e.name == m.oftype, protocol.enums)
+ if enum and "wire_type" in enum.params:
+ m_type = enum.params["wire_type"]
+ else:
+ m_type = m.oftype
+ legacy_members.append(dict(m_type=m_type, name=m.name))
+ versions[version_name]['classes'][ofclass.name] = legacy_members
+
+ for enum in protocol.enums:
+ for entry in enum.entries:
+ identifiers.add_identifier(
+ translation.loxi_name(entry.name),
+ entry.name, enum.name, entry.value, wire_version,
+ of_g.identifiers, of_g.identifiers_by_group)
+
+def populate_type_maps():
+ """
+ Use the type members in the IR to fill out the legacy type_maps.
+ """
+
+ def split_inherited_cls(cls):
+ if cls == 'of_meter_band_stats': # HACK not a subtype of of_meter_band
+ return None, None
+ for parent in sorted(type_maps.inheritance_data.keys(), reverse=True):
+ if cls.startswith(parent):
+ return (parent, cls[len(parent)+1:])
+ return None, None
+
+ def find_experimenter(parent, cls):
+ for experimenter in sorted(of_g.experimenter_name_to_id.keys(), reverse=True):
+ prefix = parent + '_' + experimenter
+ if cls.startswith(prefix) and cls != prefix:
+ return experimenter
+ return None
+
+ def find_type_value(ofclass, m_name):
+ for m in ofclass.members:
+ if isinstance(m, OFTypeMember) and m.name == m_name:
+ return m.value
+ raise KeyError("ver=%d, cls=%s, m_name=%s" % (wire_version, cls, m_name))
+
+ # Most inheritance classes: actions, instructions, etc
+ for version, protocol in loxi_globals.ir.items():
+ wire_version = version.wire_version
+ for ofclass in protocol.classes:
+ cls = ofclass.name
+ parent, subcls = split_inherited_cls(cls)
+ if not (parent and subcls):
+ continue
+ if parent == 'of_oxm':
+ type_len = find_type_value(ofclass, 'type_len')
+ oxm_class = (type_len >> 16) & 0xffff
+ if oxm_class != 0x8000:
+ # Do not include experimenter OXMs in the main table
+ val = type_maps.invalid_type
+ else:
+ val = (type_len >> 8) & 0xff
+ else:
+ val = find_type_value(ofclass, 'type')
+ type_maps.inheritance_data[parent][wire_version][subcls] = val
+
+ # Extensions (only actions for now)
+ experimenter = find_experimenter(parent, cls)
+ if parent == 'of_action' and experimenter:
+ val = find_type_value(ofclass, 'subtype')
+ type_maps.extension_action_subtype[wire_version][experimenter][cls] = val
+ if wire_version >= of_g.VERSION_1_3:
+ cls2 = parent + "_id" + cls[len(parent):]
+ type_maps.extension_action_id_subtype[wire_version][experimenter][cls2] = val
+
+ # Messages
+ for version, protocol in loxi_globals.ir.items():
+ wire_version = version.wire_version
+ for ofclass in protocol.classes:
+ cls = ofclass.name
+ # HACK (though this is what loxi_utils.class_is_message() does)
+ if not [x for x in ofclass.members if isinstance(x, OFDataMember) and x.name == 'xid']:
+ continue
+ if type_maps.class_is_virtual(cls):
+ continue
+ # HACK hide of_group subclasses from legacy c backend
+ if ofclass.name in ("of_group_add", "of_group_modify", "of_group_delete"):
+ continue
+ subcls = cls[3:]
+ val = find_type_value(ofclass, 'type')
+ if not val in type_maps.message_types[wire_version].values():
+ type_maps.message_types[wire_version][subcls] = val
+
+ # Extensions
+ experimenter = find_experimenter('of', cls)
+ if experimenter and ofclass.is_subclassof("of_experimenter"):
+ val = find_type_value(ofclass, 'subtype')
+ type_maps.extension_message_subtype[wire_version][experimenter][cls] = val
+
+ type_maps.generate_maps()
+
+def analyze_input():
+ """
+ Add information computed from the input, including offsets and
+ lengths of struct members and the set of list and action_id types.
+ """
+
+ # Generate header classes for inheritance parents
+ for wire_version, ordered_classes in of_g.ordered_classes.items():
+ classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
+ for cls in ordered_classes:
+ if cls in type_maps.inheritance_map:
+ new_cls = cls + '_header'
+ of_g.ordered_classes[wire_version].append(new_cls)
+ classes[new_cls] = classes[cls]
+
+ # Generate action_id classes for OF 1.3
+ for wire_version, ordered_classes in of_g.ordered_classes.items():
+ if not wire_version in [of_g.VERSION_1_3]:
+ continue
+ classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
+ for cls in ordered_classes:
+ if not loxi_utils.class_is_action(cls):
+ continue
+ action = cls[10:]
+ if action == '' or action == 'header':
+ continue
+ name = "of_action_id_" + action
+ members = classes["of_action"][:]
+ of_g.ordered_classes[wire_version].append(name)
+ if type_maps.action_id_is_extension(name, wire_version):
+ # Copy the base action classes thru subtype
+ members = classes["of_action_" + action][:4]
+ classes[name] = members
+
+ # @fixme If we support extended actions in OF 1.3, need to add IDs
+ # for them here
+
+ for wire_version in of_g.wire_ver_map.keys():
+ version_name = of_g.of_version_wire2name[wire_version]
+ calculate_offsets_and_lengths(
+ of_g.ordered_classes[wire_version],
+ versions[version_name]['classes'],
+ wire_version)
+
+def unify_input():
+ """
+ Create Unified View of Objects
+ """
+
+ global versions
+
+ # Add classes to unified in wire-format order so that it is easier
+ # to generate things later
+ keys = versions.keys()
+ keys.sort(reverse=True)
+ for version in keys:
+ wire_version = versions[version]["wire_version"]
+ classes = versions[version]["classes"]
+ for cls in of_g.ordered_classes[wire_version]:
+ add_class(wire_version, cls, classes[cls])
+
+
+def log_all_class_info():
+ """
+ Log the results of processing the input
+
+ Debug function
+ """
+
+ for cls in of_g.unified:
+ for v in of_g.unified[cls]:
+ if type(v) == type(0):
+ log("cls: %s. ver: %d. base len %d. %s" %
+ (str(cls), v, of_g.base_length[(cls, v)],
+ loxi_utils.class_is_var_len(cls,v) and "not fixed"
+ or "fixed"))
+ if "use_version" in of_g.unified[cls][v]:
+ log("cls %s: v %d mapped to %d" % (str(cls), v,
+ of_g.unified[cls][v]["use_version"]))
+ if "members" in of_g.unified[cls][v]:
+ for member in of_g.unified[cls][v]["members"]:
+ log(" %-20s: type %-20s. offset %3d" %
+ (member["name"], member["m_type"],
+ member["offset"]))
+
+def generate_all_files():
+ """
+ Create the files for the language target
+ """
+ for (name, fn) in lang_module.targets.items():
+ path = of_g.options.install_dir + '/' + name
+ os.system("mkdir -p %s" % os.path.dirname(path))
+ with open(path, "w") as outfile:
+ fn(outfile, os.path.basename(name))
+ print("Wrote contents for " + name)
+
+if __name__ == '__main__':
+ of_g.loxigen_log_file = open("loxigen.log", "w")
+ of_g.loxigen_dbg_file = sys.stdout
+
+ of_g.process_commandline()
+ # @fixme Use command line params to select log
+
+ if not config_sanity_check():
+ debug("Config sanity check failed\n")
+ sys.exit(1)
+
+ # Import the language file
+ lang_file = "lang_%s" % of_g.options.lang
+ lang_module = __import__(lang_file)
+
+ # If list files, just list auto-gen files to stdout and exit
+ if of_g.options.list_files:
+ for name in lang_module.targets:
+ print of_g.options.install_dir + '/' + name
+ sys.exit(0)
+
+ log("\nGenerating files for target language %s\n" % of_g.options.lang)
+
+ initialize_versions()
+ read_input()
+ populate_type_maps()
+ analyze_input()
+ unify_input()
+ order_and_assign_object_ids()
+ log_all_class_info()
+ generate_all_files()
diff --git a/c_gen/c_code_gen.py b/c_gen/c_code_gen.py
index 798f5a4..1106f2d 100644
--- a/c_gen/c_code_gen.py
+++ b/c_gen/c_code_gen.py
@@ -31,18 +31,17 @@
"""
import sys
-import of_g
+import c_gen.of_g_legacy as of_g
import c_match
from generic_utils import *
-import c_gen.c_type_maps as c_type_maps
-import loxi_front_end.type_maps as type_maps
-import loxi_front_end.flags as flags
-import loxi_utils.loxi_utils as loxi_utils
-import loxi_front_end.identifiers as identifiers
+from c_gen import flags, type_maps, c_type_maps
+import c_gen.loxi_utils_legacy as loxi_utils
+from c_gen.loxi_utils_legacy import config_check
+
+import c_gen.identifiers as identifiers
# 'property' is for queues. Could be trouble
-
################################################################
#
# Misc helper functions
diff --git a/c_gen/c_dump_gen.py b/c_gen/c_dump_gen.py
index 837ff78..a0af14e 100644
--- a/c_gen/c_dump_gen.py
+++ b/c_gen/c_dump_gen.py
@@ -33,13 +33,13 @@
"""
import sys
-import of_g
-import loxi_front_end.match as match
-import loxi_front_end.flags as flags
+import c_gen.of_g_legacy as of_g
+import c_gen.match as match
+import c_gen.flags as flags
from generic_utils import *
-import loxi_front_end.type_maps as type_maps
-import loxi_utils.loxi_utils as loxi_utils
-import loxi_front_end.identifiers as identifiers
+import c_gen.type_maps as type_maps
+import c_gen.loxi_utils_legacy as loxi_utils
+import c_gen.identifiers as identifiers
from c_test_gen import var_name_map
def gen_obj_dump_h(out, name):
diff --git a/c_gen/c_match.py b/c_gen/c_match.py
index 4001612..a45090a 100644
--- a/c_gen/c_match.py
+++ b/c_gen/c_match.py
@@ -40,8 +40,8 @@
# takes mask
import sys
-import of_g
-import loxi_front_end.match as match
+import c_gen.of_g_legacy as of_g
+import c_gen.match as match
import c_code_gen
def match_c_top_matter(out, name):
diff --git a/c_gen/c_show_gen.py b/c_gen/c_show_gen.py
index 0ec81b4..fc3edb8 100644
--- a/c_gen/c_show_gen.py
+++ b/c_gen/c_show_gen.py
@@ -33,13 +33,14 @@
"""
import sys
-import of_g
-import loxi_front_end.match as match
-import loxi_front_end.flags as flags
+import c_gen.of_g_legacy as of_g
+import c_gen.match as match
+import c_gen.flags as flags
from generic_utils import *
-import loxi_front_end.type_maps as type_maps
+import c_gen.type_maps as type_maps
import loxi_utils.loxi_utils as loxi_utils
-import loxi_front_end.identifiers as identifiers
+import c_gen.loxi_utils_legacy as loxi_utils
+import c_gen.identifiers as identifiers
from c_test_gen import var_name_map
def gen_obj_show_h(out, name):
diff --git a/c_gen/c_test_gen.py b/c_gen/c_test_gen.py
index e1b6913..6068716 100644
--- a/c_gen/c_test_gen.py
+++ b/c_gen/c_test_gen.py
@@ -59,13 +59,13 @@
"""
import sys
-import of_g
-import loxi_front_end.match as match
-import loxi_front_end.flags as flags
+import c_gen.of_g_legacy as of_g
+import c_gen.match as match
+import c_gen.flags as flags
from generic_utils import *
-import loxi_front_end.type_maps as type_maps
-import loxi_utils.loxi_utils as loxi_utils
-import loxi_front_end.identifiers as identifiers
+import c_gen.type_maps as type_maps
+import c_gen.loxi_utils_legacy as loxi_utils
+import c_gen.identifiers as identifiers
import util
import test_data
diff --git a/c_gen/c_type_maps.py b/c_gen/c_type_maps.py
index 4fc1652..024de52 100644
--- a/c_gen/c_type_maps.py
+++ b/c_gen/c_type_maps.py
@@ -29,10 +29,10 @@
# @brief C code generation for LOXI type related maps
#
-import of_g
+import c_gen.of_g_legacy as of_g
import sys
from generic_utils import *
-import loxi_front_end.type_maps as type_maps
+import c_gen.type_maps as type_maps
# Some number larger than small type values, but less then
diff --git a/c_gen/c_validator_gen.py b/c_gen/c_validator_gen.py
index 2930724..126530a 100644
--- a/c_gen/c_validator_gen.py
+++ b/c_gen/c_validator_gen.py
@@ -33,13 +33,14 @@
"""
import sys
-import of_g
-import loxi_front_end.match as match
-import loxi_front_end.flags as flags
+import c_gen.of_g_legacy as of_g
+import c_gen.match as match
+import c_gen.flags as flags
from generic_utils import *
-import loxi_front_end.type_maps as type_maps
+import c_gen.type_maps as type_maps
import loxi_utils.loxi_utils as loxi_utils
-import loxi_front_end.identifiers as identifiers
+import c_gen.loxi_utils_legacy as loxi_utils
+import c_gen.identifiers as identifiers
from c_test_gen import var_name_map
from c_code_gen import v3_match_offset_get
diff --git a/loxi_front_end/flags.py b/c_gen/flags.py
similarity index 98%
rename from loxi_front_end/flags.py
rename to c_gen/flags.py
index 47a7a7e..1fa4ae5 100644
--- a/loxi_front_end/flags.py
+++ b/c_gen/flags.py
@@ -36,7 +36,7 @@
import sys
import copy
import type_maps
-import of_g
+import c_gen.of_g_legacy as of_g
import re
# These mark idents as _not_ flags and have precedence
@@ -73,4 +73,3 @@
return True
return False
-
diff --git a/loxi_front_end/identifiers.py b/c_gen/identifiers.py
similarity index 99%
rename from loxi_front_end/identifiers.py
rename to c_gen/identifiers.py
index 7f777ec..5862967 100644
--- a/loxi_front_end/identifiers.py
+++ b/c_gen/identifiers.py
@@ -31,7 +31,6 @@
import sys
from generic_utils import *
-import of_g
##
# The value to use when an identifier is not defined for a version
diff --git a/c_gen/loci_utils.py b/c_gen/loci_utils.py
new file mode 100644
index 0000000..bc3092f
--- /dev/null
+++ b/c_gen/loci_utils.py
@@ -0,0 +1,267 @@
+import c_gen.of_g_legacy as of_g
+
+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
+
+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
diff --git a/c_gen/loxi_utils_legacy.py b/c_gen/loxi_utils_legacy.py
new file mode 100644
index 0000000..47b1430
--- /dev/null
+++ b/c_gen/loxi_utils_legacy.py
@@ -0,0 +1,545 @@
+# 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 c_gen.of_g_legacy as 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"])
diff --git a/loxi_front_end/match.py b/c_gen/match.py
similarity index 98%
rename from loxi_front_end/match.py
rename to c_gen/match.py
index c715455..611a80d 100644
--- a/loxi_front_end/match.py
+++ b/c_gen/match.py
@@ -30,9 +30,9 @@
# @fixme This still has lots of C specific code that should be moved into c_gen
import sys
-import of_g
+import c_gen.of_g_legacy as of_g
from generic_utils import *
-import loxi_utils.loxi_utils as loxi_utils
+import c_gen.loxi_utils_legacy as loxi_utils
#
# Use 1.2 match semantics for common case
@@ -556,5 +556,3 @@
debug("Type mismatch for key %s in oxm data: %s vs %s" %
(key, of_match_members[key]["m_type"], oxm_type))
sys.exit(1)
-
-
diff --git a/of_g.py b/c_gen/of_g_legacy.py
similarity index 85%
rename from of_g.py
rename to c_gen/of_g_legacy.py
index 7111259..adf5c2b 100644
--- a/of_g.py
+++ b/c_gen/of_g_legacy.py
@@ -32,7 +32,6 @@
#
import sys
-from optparse import OptionParser
# @fixme Replace with argparse
################################################################
@@ -48,84 +47,9 @@
wire_ver_map = {}
##
-# Command line options
-options = {}
-
-##
-# Command line arguments
-args = []
-
-##@var config_default
-# The default configuration dictionary for LOXI code generation
-options_default = {
- "lang" : "c",
- "version-list" : "1.0 1.1 1.2 1.3",
- "install-dir" : "loxi_output",
-}
-
-##
# The list of wire versions which are to be supported
target_version_list = []
-def lang_normalize(lang):
- """
- Normalize the representation of the language
- """
- return lang.lower()
-
-def version_list_normalize(vlist):
- """
- Normalize the version list and return as an array
- """
- out_list = []
- # @fixme Map to OF version references
- if vlist.find(',') > 0:
- vlist = vlist.split(',')
- else:
- vlist = vlist.split()
- vlist.sort()
- for ver in vlist:
- try:
- out_list.append(of_param_version_map[ver])
- except KeyError:
- sys.stderr.write("Bad version input, %s" % str(ver))
- sys.exit(1)
-
- return out_list
-
-def process_commandline(default_vals=options_default):
- """
- Set up the options dictionary
-
- @param cfg_dflt The default configuration dictionary
- @return A pair (options, args) as per parser return
- """
- global options
- global args
- global target_version_list
-
- parser = OptionParser(version="%prog 0.1")
-
- #@todo Add options via dictionary
- parser.add_option("--list-files", action="store_true", default=False,
- help="List output files generated")
- parser.add_option("-l", "--lang", "--language",
- default=default_vals["lang"],
- help="Select the target language: c, python")
- parser.add_option("-i", "--install-dir",
- default=default_vals["install-dir"],
- help="Directory to install generated files to (default %s)" % default_vals["install-dir"])
- parser.add_option("-v", "--version-list",
- default=default_vals["version-list"],
- help="Specify the versions to target as 1.0 1.1 etc")
-
- (options, args) = parser.parse_args()
-
- options.lang = lang_normalize(options.lang)
- target_version_list = version_list_normalize(options.version_list)
- target_version_list.sort()
- return (options, args)
-
##
# The dictionary of config variables related to code
#
@@ -352,12 +276,6 @@
uint64_t metadata[(OF_OBJECT_METADATA_BYTES + 7) / 8];
"""
-# LOXI intermediate representation
-# This is a hash from wire versions to OFProtocol objects.
-# See loxi_ir.py
-
-ir = {}
-
##
# LOXI identifiers
#
diff --git a/c_gen/templates/locitest/test_validator.c b/c_gen/templates/locitest/test_validator.c
index 6b9dd87..a4b55eb 100644
--- a/c_gen/templates/locitest/test_validator.c
+++ b/c_gen/templates/locitest/test_validator.c
@@ -26,9 +26,9 @@
:: # under the EPL.
::
:: include('_copyright.c')
-:: import of_g
-:: from loxi_utils import loxi_utils
-:: from loxi_front_end import type_maps
+:: import c_gen.of_g_legacy as of_g
+:: import c_gen.loxi_utils_legacy as loxi_utils
+:: from c_gen import type_maps
/**
* Test message validator
diff --git a/loxi_front_end/translation.py b/c_gen/translation.py
similarity index 99%
rename from loxi_front_end/translation.py
rename to c_gen/translation.py
index b1b3864..ef6c11b 100644
--- a/loxi_front_end/translation.py
+++ b/c_gen/translation.py
@@ -125,4 +125,3 @@
if re.match(id_from, ident):
return re.sub(id_from, id_to, ident)
return ident
-
diff --git a/loxi_front_end/type_maps.py b/c_gen/type_maps.py
similarity index 99%
rename from loxi_front_end/type_maps.py
rename to c_gen/type_maps.py
index 2f02e93..cf88e35 100644
--- a/loxi_front_end/type_maps.py
+++ b/c_gen/type_maps.py
@@ -33,10 +33,11 @@
# to wire value.
#
-import of_g
+import c_gen.of_g_legacy as of_g
import sys
from generic_utils import *
import loxi_utils.loxi_utils as loxi_utils
+import c_gen.loxi_utils_legacy as loxi_utils
invalid_type = "invalid_type"
invalid_value = "0xeeee" # Note, as a string
diff --git a/c_gen/util.py b/c_gen/util.py
index d4b25bf..54e0f80 100644
--- a/c_gen/util.py
+++ b/c_gen/util.py
@@ -30,12 +30,13 @@
"""
import os
import loxi_utils.loxi_utils as utils
+import template_utils as template_utils
templates_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'templates')
template_path = [templates_dir, templates_dir + '/locitest']
def render_template(out, name, **context):
- utils.render_template(out, name, template_path, context)
+ template_utils.render_template(out, name, template_path, context)
def render_static(out, name):
- utils.render_static(out, name, template_path)
+ template_utils.render_static(out, name, template_path)
diff --git a/cmdline.py b/cmdline.py
new file mode 100644
index 0000000..aafa019
--- /dev/null
+++ b/cmdline.py
@@ -0,0 +1,108 @@
+# 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 optparse import OptionParser
+
+from loxi_globals import OFVersions
+
+##
+# Command line options
+options = {}
+
+##
+# Command line arguments
+args = []
+
+##@var config_default
+# The default configuration dictionary for LOXI code generation
+options_default = {
+ "lang" : "c",
+ "version-list" : "1.0 1.1 1.2 1.3",
+ "install-dir" : "loxi_output",
+}
+
+def lang_normalize(lang):
+ """
+ Normalize the representation of the language
+ """
+ return lang.lower()
+
+def version_list_normalize(vlist):
+ """
+ Normalize the version list and return as an array
+ """
+ out_list = []
+ # @fixme Map to OF version references
+ if vlist.find(',') > 0:
+ vlist = vlist.split(',')
+ else:
+ vlist = vlist.split()
+ vlist.sort()
+ for ver in vlist:
+ try:
+ out_list.append(OFVersions.from_string(ver))
+ except KeyError:
+ sys.stderr.write("Bad version input, %s" % str(ver))
+ sys.exit(1)
+ return out_list
+
+def process_commandline(default_vals=options_default):
+ """
+ Set up the options dictionary
+
+ @param cfg_dflt The default configuration dictionary
+ @return A pair (options, args) as per parser return
+ """
+ global options
+ global args
+ global target_version_list
+
+ parser = OptionParser(version="%prog 0.1")
+
+ #@todo Add options via dictionary
+ parser.add_option("--list-files", action="store_true", default=False,
+ help="List output files generated")
+ parser.add_option("-l", "--lang", "--language",
+ default=default_vals["lang"],
+ help="Select the target language: c, python")
+ parser.add_option("-i", "--install-dir",
+ default=default_vals["install-dir"],
+ help="Directory to install generated files to (default %s)" % default_vals["install-dir"])
+ parser.add_option("-v", "--verbose",
+ action="store_true", default=False,
+ help="Debug output")
+
+ parser.add_option("-V", "--version-list",
+ default=default_vals["version-list"],
+ help="Specify the versions to target as 1.0 1.1 etc")
+
+ (options, args) = parser.parse_args()
+
+ options.lang = lang_normalize(options.lang)
+ target_version_list = version_list_normalize(options.version_list)
+ target_version_list.sort()
+ return (options, args, target_version_list)
diff --git a/generic_utils.py b/generic_utils.py
index ea9c589..3ed67ce 100644
--- a/generic_utils.py
+++ b/generic_utils.py
@@ -30,30 +30,10 @@
Intended to be imported into another namespace
"""
-
+import logging
import collections
import functools
import sys
-import of_g
-
-
-################################################################
-#
-# Configuration related
-#
-################################################################
-
-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
################################################################
#
@@ -63,18 +43,17 @@
def debug(obj):
"""
- Debug output to the current both the log file and debug output
- @param out_str The stringified output to write
+ Legacy logging method. Delegate to logging.debug.
+ Use logging.debug directly in the future.S
"""
- of_g.loxigen_dbg_file.write(str(obj) + "\n")
- log(obj)
+ logging.debug(obj)
def log(obj):
"""
- Log output to the current global log file
- @param out_str The stringified output to write
+ Legacy logging method. Delegate to logging.info.
+ Use logging.info directly in the future.S
"""
- of_g.loxigen_log_file.write(str(obj) + "\n")
+ logging.info(obj)
################################################################
#
diff --git a/java_gen/codegen.py b/java_gen/codegen.py
index f349f9e..c01ba68 100644
--- a/java_gen/codegen.py
+++ b/java_gen/codegen.py
@@ -29,24 +29,28 @@
@brief Main Java Generation module
"""
+import logging
import pdb
import os
import shutil
-import of_g
+import loxi_globals
from loxi_ir import *
import lang_java
import test_data
from collections import namedtuple
from import_cleaner import ImportCleaner
+import template_utils
import loxi_utils.loxi_utils as loxi_utils
import java_gen.java_model as java_model
-def gen_all_java():
- basedir= '%s/openflowj' % of_g.options.install_dir
- print "Outputting to %s" % basedir
+logger = logging.getLogger(__name__)
+
+def gen_all_java(install_dir):
+ basedir= '%s/openflowj' % install_dir
+ logger.info("Outputting to %s" % basedir)
if os.path.exists(basedir):
shutil.rmtree(basedir)
os.makedirs(basedir)
@@ -81,16 +85,16 @@
if not os.path.exists(dirname):
os.makedirs(dirname)
prefix = '//::(?=[ \t]|$)'
- print "filename: %s" % filename
+ logger.debug("rendering filename: %s" % filename)
with open(filename, "w") as f:
- loxi_utils.render_template(f, template, [self.templates_dir], context, prefix=prefix)
+ template_utils.render_template(f, template, [self.templates_dir], context, prefix=prefix)
try:
cleaner = ImportCleaner(filename)
cleaner.find_used_imports()
cleaner.rewrite_file(filename)
except:
- print 'Cannot clean imports from file %s' % filename
+ logger.info('Cannot clean imports from file %s' % filename)
def create_of_const_enums(self):
for enum in self.java_model.enums:
@@ -100,7 +104,7 @@
template='const.java', enum=enum, all_versions=self.java_model.versions)
for version in enum.versions:
- clazz = java_model.OFGenericClass(package="org.projectfloodlight.openflow.protocol.ver{}".format(version.of_version), name="{}SerializerVer{}".format(enum.name, version.of_version))
+ clazz = java_model.OFGenericClass(package="org.projectfloodlight.openflow.protocol.ver{}".format(version.dotless_version), name="{}SerializerVer{}".format(enum.name, version.dotless_version))
if enum.is_bitmask:
self.render_class(clazz=clazz, template="const_set_serializer.java", enum=enum, version=version)
@@ -133,9 +137,9 @@
template='of_virtual_class.java', version=java_class.version, msg=java_class,
impl_class=java_class.name, model=self.java_model)
else:
- print "Class %s virtual but no discriminator" % java_class.name
+ logger.warn("Class %s virtual but no discriminator" % java_class.name)
else:
- print "Class %s ignored by generate_class" % java_class.name
+ logger.info("Class %s ignored by generate_class" % java_class.name)
def create_unit_test(self, unit_tests):
if unit_tests.has_test_data:
@@ -158,4 +162,4 @@
def copy_prewrite_tree(basedir):
""" Recursively copy the directory structure from ./java_gen/pre-write
into $basedir"""
- print "Copying pre-written files into %s" % basedir
+ logger.info("Copying pre-written files into %s" % basedir)
diff --git a/java_gen/java_model.py b/java_gen/java_model.py
index a463d69..824a320 100644
--- a/java_gen/java_model.py
+++ b/java_gen/java_model.py
@@ -36,16 +36,17 @@
import re
from generic_utils import find, memoize, OrderedSet, OrderedDefaultDict
-import of_g
+from loxi_globals import OFVersions
+import loxi_globals
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
+logger = logging.getLogger(__name__)
+
class JavaModel(object):
# registry for enums that should not be generated
# set(${java_enum_name})
@@ -231,26 +232,12 @@
@property
@memoize
def versions(self):
- return OrderedSet( JavaOFVersion(raw_version) for raw_version in of_g.target_version_list )
+ return OrderedSet( JavaOFVersion(ir_version) for ir_version in OFVersions.target_versions)
@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 = [ JavaOFInterface(ir_class) for ir_class in loxi_globals.unified.classes ]
interfaces = [ i for i in interfaces if i.name not in self.interface_blacklist ]
return interfaces
@@ -270,7 +257,8 @@
name_version_enum_map = OrderedDefaultDict(lambda: OrderedDict())
for version in self.versions:
- of_protocol = of_g.ir[version.int_version]
+ logger.info("version: {}".format(version.ir_version))
+ of_protocol = loxi_globals.ir[version.ir_version]
for enum in of_protocol.enums:
name_version_enum_map[enum.name][version] = enum
@@ -325,7 +313,7 @@
factory.members.append(i)
break
return factories.values()
-
+
@memoize
def factory_of(self, interface):
for factory in self.of_factories:
@@ -359,8 +347,8 @@
@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),
+ package="org.projectfloodlight.openflow.protocol.ver{}".format(version.dotless_version),
+ name="{}Ver{}".format(self.name, version.dotless_version),
interface=self,
version=version
) for version in model.versions ]
@@ -374,7 +362,7 @@
return "build" + n[0].upper() + n[1:]
else:
return n
-
+
def of_version(self, version):
for fc in self.factory_classes:
if fc.version == version:
@@ -404,30 +392,32 @@
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)
+ def __init__(self, ir_version):
+ assert isinstance(ir_version, OFVersion)
+ self.ir_version = ir_version
+ self.int_version = self.ir_version.wire_version
@property
- def of_version(self):
- return "1" + str(int(self.int_version) - 1)
+ def dotless_version(self):
+ return self.ir_version.version.replace(".", "")
@property
def constant_version(self):
- return "OF_" + self.of_version
+ return "OF_" + self.dotless_version
def __repr__(self):
return "JavaOFVersion(%d)" % self.int_version
def __str__(self):
- return of_g.param_version_names[self.int_version]
+ return self.ir_version.version
def __hash__(self):
- return hash(self.int_version)
+ return hash(self.ir_version)
def __eq__(self, other):
if other is None or type(self) != type(other):
return False
- return (self.int_version,) == (other.int_version,)
+ return (self.ir_version,) == (other.ir_version,)
#######################################################################
### Interface
@@ -437,20 +427,21 @@
""" 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):
+ def __init__(self, ir_class):
""""
@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
+ self.ir_class = ir_class
+ self.c_name = ir_class.name
+ self.version_map = { JavaOFVersion(v): c for v,c in ir_class.version_classes.items() }
# 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"
+ self.name = java_type.name_c_to_caps_camel(self.c_name) if self.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_", "")
+ self.constant_name = self.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"
@@ -514,38 +505,42 @@
# inheritance information from the versioned lox_ir classes.
if re.match(r'OFStatsRequest$', self.name):
return ("", "OFMessage", "T extends OFStatsReply")
- elif re.match(r'OFBsnStatsRequest$', self.name):
- return ("", "OFExperimenterStatsRequest", None)
- elif re.match(r'OFBsnStatsReply$', self.name):
- return ("", "OFExperimenterStatsReply", None)
- elif re.match(r'OFBsn.+StatsRequest$', self.name):
- return ("", "OFBsnStatsRequest", None)
- elif re.match(r'OFBsn.+StatsReply$', self.name):
- return ("", "OFBsnStatsReply", None)
- 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'OF.+ErrorMsg$', self.name):
+ elif self.ir_class.is_subclassof('of_stats_request'):
+ if self.ir_class.is_subclassof('of_bsn_stats_request'):
+ return ("", "OFBsnStatsRequest", None)
+ elif self.ir_class.is_subclassof('of_experimenter_stats_request'):
+ return ("", "OFExperimenterStatsRequest", None)
+ else:
+ return ("", "OFStatsRequest<{}>".format(re.sub(r'Request$', 'Reply', self.name)), None)
+ elif self.ir_class.is_subclassof('of_stats_reply'):
+ if self.ir_class.is_subclassof('of_bsn_stats_reply'):
+ return ("", "OFBsnStatsReply", None)
+ elif self.ir_class.is_subclassof('of_experimenter_stats_reply'):
+ return ("", "OFExperimenterStatsReply", None)
+ else:
+ return ("", "OFStatsReply", None)
+ elif self.ir_class.is_subclassof('of_error_msg'):
return ("", "OFErrorMsg", None)
- elif re.match(r'OFFlow(Add|Modify(Strict)?|Delete(Strict)?)$', self.name):
+ elif self.ir_class.is_subclassof('of_flow_mod'):
return ("", "OFFlowMod", None)
- elif loxi_utils.class_is_message(self.c_name) and re.match(r'OFBsn.+$', self.name) and self.name != "OFBsnHeader":
+ elif self.ir_class.is_subclassof('of_group_mod'):
+ return ("", "OFGroupMod", None)
+ elif self.ir_class.is_subclassof('of_bsn_header'):
return ("", "OFBsnHeader", None)
- elif loxi_utils.class_is_message(self.c_name) and re.match(r'OFNicira.+$', self.name) and self.name != "OFNiciraHeader":
+ elif self.ir_class.is_subclassof('of_nicira_header'):
return ("", "OFNiciraHeader", None)
- elif self.name == "OFBsnHeader" or self.name =="OFNiciraHeader":
+ elif self.ir_class.is_subclassof('of_experimenter'):
return ("", "OFExperimenter", None)
elif re.match(r'OFMatch.*', self.name):
return ("", "Match", None)
- elif loxi_utils.class_is_message(self.c_name):
+ elif self.ir_class.is_message:
return ("", "OFMessage", None)
- elif loxi_utils.class_is_action(self.c_name):
- if re.match(r'OFActionBsn.+', self.name):
+ elif self.ir_class.is_action:
+ if self.ir_class.is_subclassof('of_action_bsn'):
return ("action", "OFActionBsn", None)
- elif re.match(r'OFActionNicira.+', self.name):
+ elif self.ir_class.is_subclassof('of_action_nicira'):
return ("action", "OFActionNicira", None)
- elif self.name == "OFActionBsn" or self.name == "OFActionNicira":
+ elif self.ir_class.is_subclassof('of_action_experimenter'):
return ("action", "OFActionExperimenter", None)
else:
return ("action", "OFAction", None)
@@ -572,6 +567,7 @@
return ("", None, None)
@property
+
@memoize
def writeable_members(self):
return [ m for m in self.members if m.is_writeable ]
@@ -594,14 +590,30 @@
all_versions = []
member_map = collections.OrderedDict()
+ member_version_map = {}
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
+ java_member = JavaMember.for_of_member(self, of_member)
if of_member.name not in member_map:
- member_map[of_member.name] = JavaMember.for_of_member(self, of_member)
+ member_map[of_member.name] = java_member
+ member_version_map[of_member.name] = version
+ else:
+ existing = member_map[of_member.name]
+
+ if existing.java_type.public_type != java_member.java_type.public_type:
+ raise Exception(
+ "Error constructing interface {}: type signatures do not match up between versions.\n"
+ " Member Name: {}\n"
+ " Existing: Version={}, Java={}, IR={}\n"
+ " New: Version={}, Java={}, IR={}"
+ .format(self.name, existing.name,
+ member_version_map[of_member.name], existing.java_type.public_type, existing.member.oftype,
+ version, java_member.java_type.public_type, java_member.member.oftype)
+ )
return tuple(m for m in member_map.values() if m.name not in model.read_blacklist[self.name])
@@ -616,7 +628,7 @@
JavaVirtualMember(self, "masked", java_type.boolean),
JavaVirtualMember(self, "canonical", java_type.make_oxm_jtype("T"))
]
- elif self.parent_interface and self.parent_interface.startswith("OFOxm"):
+ elif self.ir_class.is_subclassof("of_oxm"):
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()
@@ -628,7 +640,8 @@
custom_template=lambda builder: "OFOxm{}_getCanonical.java".format(".Builder" if builder else "")),
]
if not find(lambda x: x.name == "mask", self.ir_model_members):
- virtual_members.append(JavaVirtualMember(self, "mask", find(lambda x: x.name == "value", self.ir_model_members).java_type))
+ virtual_members.append(
+ JavaVirtualMember(self, "mask", find(lambda x: x.name == "value", self.ir_model_members).java_type))
if not find(lambda m: m.name == "version", self.ir_model_members):
virtual_members.append(JavaVirtualMember(self, "version", java_type.of_version))
@@ -682,7 +695,7 @@
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.package = "org.projectfloodlight.openflow.protocol.ver%s" % version.dotless_version
self.generated = False
@property
@@ -692,7 +705,7 @@
@property
def name(self):
- return "%sVer%s" % (self.interface.name, self.version.of_version)
+ return "%sVer%s" % (self.interface.name, self.version.dotless_version)
@property
def variable_name(self):
@@ -708,23 +721,16 @@
@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
+ return self.ir_class.base_length
@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
+ return self.ir_class.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):
@@ -814,7 +820,8 @@
@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 ]
+ return [ c for c in model.all_classes if c.version == self.version and c.ir_class.superclass
+ and c.ir_class.superclass.name == self.c_name ]
#######################################################################
### Member
@@ -969,16 +976,8 @@
@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']):
+ for version, ir_class in self.msg.ir_class.version_classes.items():
+ if not ir_class.member_by_name(self.member.name):
return False
return True
@@ -1034,9 +1033,9 @@
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,
+ first_data_file_name = "of{version}/{name}.data".format(version=java_class.version.dotless_version,
name=java_class.c_name[3:])
- glob_file_name = "of{version}/{name}__*.data".format(version=java_class.version.of_version,
+ glob_file_name = "of{version}/{name}__*.data".format(version=java_class.version.dotless_version,
name=java_class.c_name[3:])
test_class_name = self.java_class.name + "Test"
self.test_units = []
@@ -1074,7 +1073,7 @@
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,
+ self.data_file_name = "of{version}/{name}.data".format(version=java_class.version.dotless_version,
name=java_class.c_name[3:])
else:
self.data_file_name = file_name
diff --git a/java_gen/java_type.py b/java_gen/java_type.py
index 333efd8..d1a9e67 100644
--- a/java_gen/java_type.py
+++ b/java_gen/java_type.py
@@ -4,9 +4,9 @@
import subprocess
import time
+import loxi_globals
from generic_utils import memoize
import loxi_utils.loxi_utils as loxi_utils
-import of_g
def erase_type_annotation(class_name):
m=re.match(r'(.*)<.*>', class_name)
@@ -145,7 +145,7 @@
ver = ANY if version is None else version.int_version
if not "version" in arguments:
- arguments["version"] = version.of_version
+ arguments["version"] = version.dotless_version
def lookup(ver, pub_type):
if (ver, pub_type) in self.ops:
@@ -328,6 +328,8 @@
.op(read='ChannelUtilsVer$version.readOFMatch(bb)', \
write='$name.writeTo(bb)',
default="OFFactoryVer$version.MATCH_WILDCARD_ALL");
+group_mod_cmd = JType('OFGroupModCommand', 'short') \
+ .op(version=ANY, read="bb.readShort()", write="bb.writeShort($name)")
flow_mod_cmd = JType('OFFlowModCommand', 'short') \
.op(version=1, read="bb.readShort()", write="bb.writeShort($name)") \
.op(version=ANY, read="bb.readByte()", write="bb.writeByte($name)")
@@ -603,18 +605,22 @@
'of_action_set_tp_src': { 'tp_port': transport_port },
'of_action_set_vlan_pcp': { 'vlan_pcp': vlan_pcp },
'of_action_set_vlan_vid': { 'vlan_vid': vlan_vid },
+
+ 'of_group_mod' : { 'command' : group_mod_cmd },
+ 'of_group_add' : { 'command' : group_mod_cmd },
+ 'of_group_modify' : { 'command' : group_mod_cmd },
+ 'of_group_delete' : { 'command' : group_mod_cmd },
+
+ 'of_bucket' : { 'watch_group': of_group },
}
@memoize
def enum_java_types():
enum_types = {}
-
- for protocol in of_g.ir.values():
- for enum in protocol.enums:
- java_name = name_c_to_caps_camel(re.sub(r'_t$', "", enum.name))
-
- enum_types[enum.name] = gen_enum_jtype(java_name, enum.is_bitmask)
+ for enum in loxi_globals.unified.enums:
+ java_name = name_c_to_caps_camel(re.sub(r'_t$', "", enum.name))
+ enum_types[enum.name] = gen_enum_jtype(java_name, enum.is_bitmask)
return enum_types
def make_match_field_jtype(sub_type_name="?"):
diff --git a/java_gen/pre-written/pom.xml b/java_gen/pre-written/pom.xml
index e516b6e..cc480df 100644
--- a/java_gen/pre-written/pom.xml
+++ b/java_gen/pre-written/pom.xml
@@ -60,6 +60,16 @@
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-core</artifactId>
+ <version>1.0.13</version>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <version>1.0.13</version>
+ </dependency>
</dependencies>
<build>
<plugins>
diff --git a/java_gen/pre-written/src/test/resources/logback-test.xml b/java_gen/pre-written/src/test/resources/logback-test.xml
new file mode 100644
index 0000000..e759962
--- /dev/null
+++ b/java_gen/pre-written/src/test/resources/logback-test.xml
@@ -0,0 +1,13 @@
+<configuration scan="true">
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <pattern>%d{HH:mm:ss.SSS} %level [%logger{20}:%thread] %msg%n</pattern>
+ </encoder>
+ </appender>
+ <root level="INFO">
+ <appender-ref ref="STDOUT" />
+ </root>
+ <logger name="org" level="WARN"/>
+ <logger name="LogService" level="WARN"/> <!-- Restlet access logging -->
+ <logger name="org.projectfloodlight.openflow" level="DEBUG"/>
+</configuration>
diff --git a/java_gen/templates/_field_accessors.java b/java_gen/templates/_field_accessors.java
index 030388c..41ac66f 100644
--- a/java_gen/templates/_field_accessors.java
+++ b/java_gen/templates/_field_accessors.java
@@ -11,7 +11,7 @@
@Override
public ${prop.java_type.public_type} ${prop.getter_name}()${ "" if prop in msg.members else "throws UnsupportedOperationException"} {
//:: if prop in msg.members:
-//:: version_prop = msg.get_member(prop.name)
+//:: version_prop = msg.member_by_name(prop.name)
//:: if version_prop.is_fixed_value:
return ${version_prop.enum_value};
//:: elif version_prop.is_length_value:
diff --git a/java_gen/templates/const.java b/java_gen/templates/const.java
index 413c946..a7786f4 100644
--- a/java_gen/templates/const.java
+++ b/java_gen/templates/const.java
@@ -26,7 +26,6 @@
//:: # under the EPL.
//::
//:: import itertools
-//:: import of_g
//:: include('_copyright.java')
//:: include('_autogen.java')
diff --git a/java_gen/templates/const_serializer.java b/java_gen/templates/const_serializer.java
index 12ff28a..03ca8ac 100644
--- a/java_gen/templates/const_serializer.java
+++ b/java_gen/templates/const_serializer.java
@@ -26,7 +26,6 @@
//:: # under the EPL.
//::
//:: import itertools
-//:: import of_g
//:: include('_copyright.java')
//:: include('_autogen.java')
diff --git a/java_gen/templates/const_set_serializer.java b/java_gen/templates/const_set_serializer.java
index 4c624ee..61592a6 100644
--- a/java_gen/templates/const_set_serializer.java
+++ b/java_gen/templates/const_set_serializer.java
@@ -26,7 +26,6 @@
//:: # under the EPL.
//::
//:: import itertools
-//:: import of_g
//:: include('_copyright.java')
//:: include('_autogen.java')
diff --git a/java_gen/templates/of_class.java b/java_gen/templates/of_class.java
index 36d407c..4057b3d 100644
--- a/java_gen/templates/of_class.java
+++ b/java_gen/templates/of_class.java
@@ -28,7 +28,6 @@
//:: from loxi_ir import *
//:: import os
//:: import itertools
-//:: import of_g
//:: include('_copyright.java')
//:: include('_autogen.java')
@@ -50,6 +49,9 @@
//:: #endif
//:: for prop in msg.data_members:
+ //:: if prop.java_type.public_type != msg.interface.member_by_name(prop.name).java_type.public_type:
+ //:: raise Exception("Interface and Class types do not match up: C: {} <-> I: {}".format(prop.java_type.public_type, msg.interface.member_by_name(prop.name).java_type.public_type))
+ //:: #endif
//:: if prop.default_value:
private final static ${prop.java_type.public_type} ${prop.default_name} = ${prop.default_value};
//:: #endif
diff --git a/java_gen/templates/of_factories.java b/java_gen/templates/of_factories.java
index f8c9a80..f9ec015 100644
--- a/java_gen/templates/of_factories.java
+++ b/java_gen/templates/of_factories.java
@@ -26,7 +26,6 @@
//:: # under the EPL.
//::
//:: import itertools
-//:: import of_g
//:: include('_copyright.java')
//:: include('_autogen.java')
@@ -43,13 +42,13 @@
switch(version) {
//:: for v in versions:
case ${v.constant_version}:
- return org.projectfloodlight.openflow.protocol.ver${v.of_version}.OFFactoryVer${v.of_version}.INSTANCE;
+ return org.projectfloodlight.openflow.protocol.ver${v.dotless_version}.OFFactoryVer${v.dotless_version}.INSTANCE;
//:: #endfor
default:
throw new IllegalArgumentException("Unknown version: "+version);
}
}
-
+
private static class GenericReader implements OFMessageReader<OFMessage> {
public OFMessage readFrom(ChannelBuffer bb) throws OFParseError {
short wireVersion = U8.f(bb.getByte(0));
@@ -57,7 +56,7 @@
switch (wireVersion) {
//:: for v in versions:
case ${v.int_version}:
- factory = org.projectfloodlight.openflow.protocol.ver${v.of_version}.OFFactoryVer${v.of_version}.INSTANCE;
+ factory = org.projectfloodlight.openflow.protocol.ver${v.dotless_version}.OFFactoryVer${v.dotless_version}.INSTANCE;
break;
//:: #endfor
default:
diff --git a/java_gen/templates/of_factory_class.java b/java_gen/templates/of_factory_class.java
index eef1e04..deb6e53 100644
--- a/java_gen/templates/of_factory_class.java
+++ b/java_gen/templates/of_factory_class.java
@@ -26,7 +26,6 @@
//:: # under the EPL.
//::
//:: import itertools
-//:: import of_g
//:: import re
//:: include('_copyright.java')
@@ -47,7 +46,7 @@
//:: for name, clazz in factory.interface.sub_factories.items():
public ${clazz} ${name}() {
- return ${clazz}Ver${factory.version.of_version}.INSTANCE;
+ return ${clazz}Ver${factory.version.dotless_version}.INSTANCE;
}
//:: #endfor
diff --git a/java_gen/templates/of_factory_interface.java b/java_gen/templates/of_factory_interface.java
index 9a77aa7..3694530 100644
--- a/java_gen/templates/of_factory_interface.java
+++ b/java_gen/templates/of_factory_interface.java
@@ -26,7 +26,6 @@
//:: # under the EPL.
//::
//:: import itertools
-//:: import of_g
//:: import re
//:: include('_copyright.java')
diff --git a/java_gen/templates/of_interface.java b/java_gen/templates/of_interface.java
index 98d7b9e..a515ad1 100644
--- a/java_gen/templates/of_interface.java
+++ b/java_gen/templates/of_interface.java
@@ -27,7 +27,6 @@
//::
//:: import itertools
//:: import re
-//:: import of_g
//:: include('_copyright.java')
//:: include('_autogen.java')
diff --git a/java_gen/templates/of_virtual_class.java b/java_gen/templates/of_virtual_class.java
index 55ccc5e..2c31c75 100644
--- a/java_gen/templates/of_virtual_class.java
+++ b/java_gen/templates/of_virtual_class.java
@@ -28,7 +28,6 @@
//:: from loxi_ir import *
//:: import os
//:: import itertools
-//:: import of_g
//:: include('_copyright.java')
//:: include('_autogen.java')
@@ -87,7 +86,7 @@
//:: if not model.generate_class(sub):
// skip ${sub.name} - excluded from generation
//:: else:
-//:: m = sub.get_member(prop.name)
+//:: m = sub.member_by_name(prop.name)
//:: if not m.is_fixed_value:
//:: raise Exception("subtype %s of %s does not have fixed value for discriminator %s" %
//:: (sub.name, msg.name, prop.name))
diff --git a/java_gen/templates/unit_test.java b/java_gen/templates/unit_test.java
index cd85a74..ad2c3b8 100644
--- a/java_gen/templates/unit_test.java
+++ b/java_gen/templates/unit_test.java
@@ -27,7 +27,6 @@
//::
//:: from loxi_ir import *
//:: import itertools
-//:: import of_g
//:: import java_gen.java_model as java_model
//:: include('_copyright.java')
@@ -40,6 +39,9 @@
import org.junit.Test;
import static org.junit.Assert.*;
+import org.hamcrest.CoreMatchers;
+
+
public class ${test.name} {
//:: factory = java_model.model.factory_of(test.interface)
//:: var_type = msg.interface.name
@@ -67,7 +69,7 @@
byte[] written = new byte[bb.readableBytes()];
bb.readBytes(written);
- assertArrayEquals(${msg.constant_name}_SERIALIZED, written);
+ assertThat(written, CoreMatchers.equalTo(${msg.constant_name}_SERIALIZED));
}
@Test
@@ -102,7 +104,7 @@
byte[] written = new byte[bb.readableBytes()];
bb.readBytes(written);
- assertArrayEquals(${msg.constant_name}_SERIALIZED, written);
+ assertThat(written, CoreMatchers.equalTo(${msg.constant_name}_SERIALIZED));
}
}
diff --git a/lang_c.py b/lang_c.py
index 9cfc2fa..762f82c 100644
--- a/lang_c.py
+++ b/lang_c.py
@@ -33,6 +33,8 @@
"""
import os
+import c_gen.of_g_legacy as of_g
+import c_gen.build_of_g as build_of_g
import c_gen.c_code_gen as c_code_gen
import c_gen.c_test_gen as c_test_gen
import c_gen.c_dump_gen as c_dump_gen
@@ -40,6 +42,7 @@
import c_gen.c_validator_gen as c_validator_gen
import c_gen.util
import loxi_utils.loxi_utils as loxi_utils
+import template_utils
def static(out, name):
c_gen.util.render_template(out, os.path.basename(name))
@@ -112,7 +115,61 @@
'locitest/Makefile': static,
}
-def generate():
+################################################################
+#
+# Configuration related
+#
+################################################################
+
+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 config_sanity_check():
+ """
+ Check the configuration for basic consistency
+
+ @fixme Needs update for generic language support
+ """
+
+ rv = True
+ # For now, only "error" supported for get returns
+ if config_check("copy_semantics") != "read":
+ debug("Only 'read' is supported for copy_semantics");
+ rv = False
+ if config_check("get_returns") != "error":
+ debug("Only 'error' is supported for get-accessor return types\m");
+ rv = False
+ if not config_check("use_fn_ptrs") and not config_check("gen_unified_fns"):
+ debug("Must have gen_fn_ptrs and/or gen_unified_fns set in config")
+ rv = False
+ if config_check("use_obj_id"):
+ debug("use_obj_id is set but not yet supported (change \
+config_sanity_check if it is)")
+ rv = False
+ if config_check("gen_unified_macros") and config_check("gen_unified_fns") \
+ and config_check("gen_unified_macro_lower"):
+ debug("Conflict: Cannot generate unified functions and lower case \
+unified macros")
+ rv = False
+
+ return rv
+
+def generate(install_dir):
+ build_of_g.initialize_versions()
+ build_of_g.build_ordered_classes()
+ build_of_g.populate_type_maps()
+ build_of_g.analyze_input()
+ build_of_g.unify_input()
+ build_of_g.order_and_assign_object_ids()
for (name, fn) in targets.items():
- with loxi_utils.open_output(name) as outfile:
+ with template_utils.open_output(install_dir, name) as outfile:
fn(outfile, os.path.basename(name))
diff --git a/lang_java.py b/lang_java.py
index 3891076..f103330 100644
--- a/lang_java.py
+++ b/lang_java.py
@@ -31,5 +31,5 @@
import java_gen.codegen as java_codegen
-def generate():
- java_codegen.gen_all_java()
+def generate(install_dir):
+ java_codegen.gen_all_java(install_dir)
diff --git a/lang_python.py b/lang_python.py
index 9c9d13e..f40cecc 100644
--- a/lang_python.py
+++ b/lang_python.py
@@ -62,10 +62,13 @@
"""
import os
+from loxi_globals import OFVersions
+import loxi_globals
import loxi_utils.loxi_utils as loxi_utils
import py_gen
import py_gen.util
import py_gen.codegen
+import template_utils
versions = {
1: "of10",
@@ -100,10 +103,10 @@
targets['%s/%s/__init__.py' % (prefix, subdir)] = make_gen('init', version)
for module in modules[version]:
filename = '%s/%s/%s.py' % (prefix, subdir, module)
- targets[filename] = make_gen(module, version)
+ targets[filename] = make_gen(module, OFVersions.from_wire(version))
-def generate():
+def generate(install_dir):
py_gen.codegen.init()
for (name, fn) in targets.items():
- with loxi_utils.open_output(name) as outfile:
+ with template_utils.open_output(install_dir, name) as outfile:
fn(outfile, os.path.basename(name))
diff --git a/loxi_front_end/frontend.py b/loxi_front_end/frontend.py
index 35c1e44..a84508a 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(name, 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(name, 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 73%
rename from loxi_ir.py
rename to loxi_front_end/frontend_ir.py
index 0fe5497..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',
@@ -47,28 +45,11 @@
"""
One input file
-@param name Name of the input file
@param wire_versions Set of integer wire versions this file applies to
@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', ['name', '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
@@ -83,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
@@ -162,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_globals.py b/loxi_globals.py
new file mode 100644
index 0000000..93b24b5
--- /dev/null
+++ b/loxi_globals.py
@@ -0,0 +1,72 @@
+# 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 loxi_ir import *
+
+#######################################################################
+### OFVersion registry
+#######################################################################
+
+class OFVersions:
+ VERSION_1_0 = OFVersion("1.0", 1)
+ VERSION_1_1 = OFVersion("1.1", 2)
+ VERSION_1_2 = OFVersion("1.2", 3)
+ VERSION_1_3 = OFVersion("1.3", 4)
+
+ all_supported = (
+ VERSION_1_0,
+ VERSION_1_1,
+ VERSION_1_2,
+ VERSION_1_3,
+ )
+
+ wire_version_map = { v.wire_version : v for v in all_supported }
+ version_string_map = { v.version : v for v in all_supported }
+
+ target_versions = []
+
+ @staticmethod
+ def from_wire(w):
+ return OFVersions.wire_version_map[w]
+
+ @staticmethod
+ def from_string(s):
+ return OFVersions.version_string_map[s]
+
+ @staticmethod
+ def from_strings(*strings):
+ return tuple(OFVersions.version_string_map[s] for s in strings)
+
+
+
+
+#######################################################################
+### OFVersions
+#######################################################################
+
+# map OFVersion -> OFProtocol
+ir = {}
diff --git a/loxi_front_end/c_parse_utils.py b/loxi_ir/__init__.py
similarity index 66%
rename from loxi_front_end/c_parse_utils.py
rename to loxi_ir/__init__.py
index 5e8d471..b4f1c05 100644
--- a/loxi_front_end/c_parse_utils.py
+++ b/loxi_ir/__init__.py
@@ -25,27 +25,7 @@
# EPL for the specific language governing permissions and limitations
# under the EPL.
-##
-# @brief Utilities related to parsing C files
-#
-import of_g
-
-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
+# 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/loxi_utils/loxi_utils.py b/loxi_utils/loxi_utils.py
index 059c363..f4cd950 100644
--- a/loxi_utils/loxi_utils.py
+++ b/loxi_utils/loxi_utils.py
@@ -33,41 +33,12 @@
These may need to be sorted out into language specific functions
"""
+import re
import sys
-import os
-import of_g
-import tenjin
+
+import loxi_globals
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:
#
@@ -90,11 +61,24 @@
#
#
+class NoneClass(object):
+ def is_instanceof(self, x):
+ return False
+none_item = NoneClass()
+
+def _unified_by_name(cls):
+ c = loxi_globals.unified.class_by_name(cls)
+ return c if c is not None else none_item
+
+@memoize
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"
+ if cls == "of_header":
+ return False
+ else:
+ return _unified_by_name(cls).is_instanceof("of_header")
def class_is_tlv16(cls):
"""
@@ -132,9 +116,7 @@
"""
Return True if cls_name is an OXM object
"""
- if cls.find("of_oxm") == 0:
- return True
- return False
+ return _unified_by_name(cls).is_instanceof("of_oxm")
def class_is_action(cls):
"""
@@ -145,17 +127,7 @@
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
+ return _unified_by_name(cls).is_instanceof("of_action")
def class_is_action_id(cls):
"""
@@ -166,77 +138,44 @@
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
+ return _unified_by_name(cls).is_instanceof("of_action_id")
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
+ return _unified_by_name(cls).is_instanceof("of_instruction")
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
+ return _unified_by_name(cls).is_instanceof("of_meter_band")
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
+ return _unified_by_name(cls).is_instanceof("of_hello_elem")
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
+ return _unified_by_name(cls).is_instanceof("of_queue_prop")
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
+ return _unified_by_name(cls).is_instanceof("of_table_feature_prop")
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"]
+ u = _unified_by_name(cls)
+ return u.is_instanceof("of_stats_request") or u.ir_instanceof("of_stats_reply")
def class_is_list(cls):
"""
@@ -249,311 +188,15 @@
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())
+ return _unified_by_name(re.sub(r'_t$', '', m_type)) != none_item
@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
+ enums = loxi_globals.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")
diff --git a/loxigen.py b/loxigen.py
index 43ff2a8..700168a 100755
--- a/loxigen.py
+++ b/loxigen.py
@@ -69,248 +69,27 @@
"""
-import sys
-
+from collections import OrderedDict, defaultdict
+import copy
+import glob
+from optparse import OptionParser
+import os
import re
import string
-import os
-import glob
-import copy
-import collections
-import of_g
-import loxi_front_end.type_maps as type_maps
+import sys
+
+import cmdline
+from loxi_globals import OFVersions
+import loxi_globals
import loxi_utils.loxi_utils as loxi_utils
-import loxi_front_end.c_parse_utils as c_parse_utils
-import loxi_front_end.identifiers as identifiers
import pyparsing
import loxi_front_end.parser as parser
-import loxi_front_end.translation as translation
import loxi_front_end.frontend as frontend
-from loxi_ir import *
+import loxi_ir
from generic_utils import *
root_dir = os.path.dirname(os.path.realpath(__file__))
-# TODO: Put these in a class so they get documented
-
-## Dict indexed by version giving all info related to version
-#
-# This is local; after processing, the information is stored in
-# of_g variables.
-versions = {}
-
-def config_sanity_check():
- """
- Check the configuration for basic consistency
-
- @fixme Needs update for generic language support
- """
-
- rv = True
- # For now, only "error" supported for get returns
- if config_check("copy_semantics") != "read":
- debug("Only 'read' is supported for copy_semantics");
- rv = False
- if config_check("get_returns") != "error":
- debug("Only 'error' is supported for get-accessor return types\m");
- rv = False
- if not config_check("use_fn_ptrs") and not config_check("gen_unified_fns"):
- debug("Must have gen_fn_ptrs and/or gen_unified_fns set in config")
- rv = False
- if config_check("use_obj_id"):
- debug("use_obj_id is set but not yet supported (change \
-config_sanity_check if it is)")
- rv = False
- if config_check("gen_unified_macros") and config_check("gen_unified_fns") \
- and config_check("gen_unified_macro_lower"):
- debug("Conflict: Cannot generate unified functions and lower case \
-unified macros")
- rv = False
-
- return rv
-
-def add_class(wire_version, cls, members):
- """
- Process a class for the given version and update the unified
- list of classes as needed.
-
- @param wire_version The wire version for this class defn
- @param cls The name of the class being added
- @param members The list of members with offsets calculated
- """
- memid = 0
-
- sig = loxi_utils.class_signature(members)
- if cls in of_g.unified:
- uc = of_g.unified[cls]
- if wire_version in uc:
- debug("Error adding %s to unified. Wire ver %d exists" %
- (cls, wire_version))
- sys.exit(1)
- uc[wire_version] = {}
- # Check for a matching signature
- for wver in uc:
- if type(wver) != type(0): continue
- if wver == wire_version: continue
- if not "use_version" in uc[wver]:
- if sig == loxi_utils.class_signature(uc[wver]["members"]):
- log("Matched %s, ver %d to ver %d" %
- (cls, wire_version, wver))
- # have a match with existing version
- uc[wire_version]["use_version"] = wver
- # What else to do?
- return
- else: # Haven't seen this entry before
- log("Adding %s to unified list, ver %d" % (cls, wire_version))
- of_g.unified[cls] = dict(union={})
- uc = of_g.unified[cls]
-
- # At this point, need to add members for this version
- uc[wire_version] = dict(members = members)
-
- # Per member processing:
- # Add to union list (I'm sure there's a better way)
- # Check if it's a list
- union = uc["union"]
- if not cls in of_g.ordered_members:
- of_g.ordered_members[cls] = []
- for member in members:
- m_name = member["name"]
- m_type = member["m_type"]
- if m_name.find("pad") == 0:
- continue
- if m_name in union:
- if not m_type == union[m_name]["m_type"]:
- debug("ERROR: CLASS: %s. VERSION %d. MEMBER: %s. TYPE: %s" %
- (cls, wire_version, m_name, m_type))
- debug(" Type conflict adding member to unified set.")
- debug(" Current union[%s]:" % m_name)
- debug(union[m_name])
- sys.exit(1)
- else:
- union[m_name] = dict(m_type=m_type, memid=memid)
- memid += 1
- if not m_name in of_g.ordered_members[cls]:
- of_g.ordered_members[cls].append(m_name)
-
-def update_offset(cls, wire_version, name, offset, m_type):
- """
- Update (and return) the offset based on type.
- @param cls The parent class
- @param wire_version The wire version being processed
- @param name The name of the data member
- @param offset The current offset
- @param m_type The type declaration being processed
- @returns A pair (next_offset, len_update) next_offset is the new offset
- of the next object or -1 if this is a var-length object. len_update
- is the increment that should be added to the length. Note that (for
- of_match_v3) it is variable length, but it adds 8 bytes to the fixed
- length of the object
- If offset is already -1, do not update
- Otherwise map to base type and count and update (if possible)
- """
- if offset < 0: # Don't update offset once set to -1
- return offset, 0
-
- count, base_type = c_parse_utils.type_dec_to_count_base(m_type)
-
- len_update = 0
- if base_type in of_g.of_mixed_types:
- base_type = of_g.of_mixed_types[base_type][wire_version]
-
- base_class = base_type[:-2]
- if (base_class, wire_version) in of_g.is_fixed_length:
- bytes = of_g.base_length[(base_class, wire_version)]
- else:
- if base_type == "of_match_v3_t":
- # This is a special case: it has non-zero min length
- # but is variable length
- bytes = -1
- len_update = 8
- elif base_type in of_g.of_base_types:
- bytes = of_g.of_base_types[base_type]["bytes"]
- else:
- print "UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type)
- log("UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type))
- bytes = -1
-
- # If bytes
- if bytes > 0:
- len_update = count * bytes
-
- if bytes == -1:
- return -1, len_update
-
- return offset + (count * bytes), len_update
-
-def calculate_offsets_and_lengths(ordered_classes, classes, wire_version):
- """
- Generate the offsets for fixed offset class members
- Also calculate the class_sizes when possible.
-
- @param classes The classes to process
- @param wire_version The wire version for this set of classes
-
- Updates global variables
- """
-
- lists = set()
-
- # Generate offsets
- for cls in ordered_classes:
- fixed_offset = 0 # The last "good" offset seen
- offset = 0
- last_offset = 0
- last_name = "-"
- for member in classes[cls]:
- m_type = member["m_type"]
- name = member["name"]
- if last_offset == -1:
- if name == "pad":
- log("Skipping pad for special offset for %s" % cls)
- else:
- log("SPECIAL OFS: Member %s (prev %s), class %s ver %d" %
- (name, last_name, cls, wire_version))
- if (((cls, name) in of_g.special_offsets) and
- (of_g.special_offsets[(cls, name)] != last_name)):
- debug("ERROR: special offset prev name changed")
- debug(" cls %s. name %s. version %d. was %s. now %s" %
- cls, name, wire_version,
- of_g.special_offsets[(cls, name)], last_name)
- sys.exit(1)
- of_g.special_offsets[(cls, name)] = last_name
-
- member["offset"] = offset
- if m_type.find("list(") == 0:
- (list_name, base_type) = loxi_utils.list_name_extract(m_type)
- lists.add(list_name)
- member["m_type"] = list_name + "_t"
- offset = -1
- elif m_type.find("struct") == 0:
- debug("ERROR found struct: %s.%s " % (cls, name))
- sys.exit(1)
- elif m_type == "octets":
- log("offset gen skipping octets: %s.%s " % (cls, name))
- offset = -1
- else:
- offset, len_update = update_offset(cls, wire_version, name,
- offset, m_type)
- if offset != -1:
- fixed_offset = offset
- else:
- fixed_offset += len_update
- log("offset is -1 for %s.%s version %d " %
- (cls, name, wire_version))
- last_offset = offset
- last_name = name
- of_g.base_length[(cls, wire_version)] = fixed_offset
- if (offset != -1):
- of_g.is_fixed_length.add((cls, wire_version))
- for list_type in lists:
- classes[list_type] = []
- of_g.ordered_classes[wire_version].append(list_type)
- of_g.base_length[(list_type, wire_version)] = 0
-
def process_input_file(filename):
"""
Process an input file
@@ -339,71 +118,6 @@
return ofinput
-def order_and_assign_object_ids():
- """
- Order all classes and assign object ids to all classes.
-
- This is done to promote a reasonable order of the objects, putting
- messages first followed by non-messages. No assumptions should be
- made about the order, nor about contiguous numbering. However, the
- numbers should all be reasonably small allowing arrays indexed by
- these enum values to be defined.
- """
-
- # Generate separate message and non-message ordered lists
- for cls in of_g.unified:
- if loxi_utils.class_is_message(cls):
- of_g.ordered_messages.append(cls)
- elif loxi_utils.class_is_list(cls):
- of_g.ordered_list_objects.append(cls)
- else:
- of_g.ordered_non_messages.append(cls)
-
- of_g.ordered_messages.sort()
- of_g.ordered_pseudo_objects.sort()
- of_g.ordered_non_messages.sort()
- of_g.ordered_list_objects.sort()
- of_g.standard_class_order.extend(of_g.ordered_messages)
- of_g.standard_class_order.extend(of_g.ordered_non_messages)
- of_g.standard_class_order.extend(of_g.ordered_list_objects)
-
- # This includes pseudo classes for which most code is not generated
- of_g.all_class_order.extend(of_g.ordered_messages)
- of_g.all_class_order.extend(of_g.ordered_non_messages)
- of_g.all_class_order.extend(of_g.ordered_list_objects)
- of_g.all_class_order.extend(of_g.ordered_pseudo_objects)
-
- # Assign object IDs
- for cls in of_g.ordered_messages:
- of_g.unified[cls]["object_id"] = of_g.object_id
- of_g.object_id += 1
- for cls in of_g.ordered_non_messages:
- of_g.unified[cls]["object_id"] = of_g.object_id
- of_g.object_id += 1
- for cls in of_g.ordered_list_objects:
- of_g.unified[cls]["object_id"] = of_g.object_id
- of_g.object_id += 1
- for cls in of_g.ordered_pseudo_objects:
- of_g.unified[cls] = {}
- of_g.unified[cls]["object_id"] = of_g.object_id
- of_g.object_id += 1
-
-
-def initialize_versions():
- """
- Create an empty datastructure for each target version.
- """
-
- for wire_version in of_g.target_version_list:
- version_name = of_g.of_version_wire2name[wire_version]
- of_g.wire_ver_map[wire_version] = version_name
- versions[version_name] = dict(
- version_name = version_name,
- wire_version = wire_version,
- classes = {})
- of_g.ordered_classes[wire_version] = []
-
-
def read_input():
"""
Read in from files given on command line and update global state
@@ -411,7 +125,7 @@
@fixme Should select versions to support from command line
"""
- ofinputs_by_version = collections.defaultdict(lambda: [])
+ ofinputs_by_version = defaultdict(lambda: [])
filenames = sorted(glob.glob("%s/openflow_input/*" % root_dir))
# Ignore emacs backup files
@@ -422,258 +136,51 @@
for filename in filenames:
log("Processing struct file: " + filename)
ofinput = process_input_file(filename)
- all_ofinputs.append(ofinput)
+
for wire_version in ofinput.wire_versions:
ofinputs_by_version[wire_version].append(ofinput)
+ return ofinputs_by_version
- # Merge input files into per-version IR
+def build_ir(ofinputs_by_version):
+ classes = []
+ enums = []
for wire_version, ofinputs in ofinputs_by_version.items():
- ofprotocol = OFProtocol(wire_version=wire_version, classes=[], enums=[])
- for ofinput in ofinputs:
- ofprotocol.classes.extend(ofinput.classes)
- ofprotocol.enums.extend(ofinput.enums)
- ofprotocol.classes.sort(key=lambda ofclass: ofclass.name)
- of_g.ir[wire_version] = ofprotocol
+ version = OFVersions.from_wire(wire_version)
+ ofprotocol = loxi_ir.build_protocol(version, ofinputs)
+ loxi_globals.ir[version] = ofprotocol
- # Extract enums
- # An input file can refer to an enum in another file
- enums_by_version = { ver: {} for ver in ofinputs_by_version }
- for ofinput in all_ofinputs:
- for wire_version in ofinput.wire_versions:
- for enum in ofinput.enums:
- enums_by_version[wire_version][enum.name] = enum
+ loxi_globals.unified = loxi_ir.build_unified_ir(loxi_globals.ir)
- # Populate legacy maps
- for ofinput in all_ofinputs:
- for wire_version in ofinput.wire_versions:
- version_name = of_g.of_version_wire2name[wire_version]
-
- for ofclass in ofinput.classes:
- of_g.ordered_classes[wire_version].append(ofclass.name)
- legacy_members = []
- pad_count = 0
- for m in ofclass.members:
- if type(m) == OFPadMember:
- m_name = 'pad%d' % pad_count
- if m_name == 'pad0': m_name = 'pad'
- legacy_members.append(dict(m_type='uint8_t[%d]' % m.length,
- name=m_name))
- pad_count += 1
- else:
- # HACK the C backend does not yet support of_oxm_t
- if m.oftype == 'of_oxm_t':
- m_type = 'of_octets_t'
- else:
- enum = enums_by_version[wire_version].get(m.oftype)
- if enum and "wire_type" in enum.params:
- m_type = enum.params["wire_type"]
- else:
- m_type = m.oftype
- legacy_members.append(dict(m_type=m_type, name=m.name))
- versions[version_name]['classes'][ofclass.name] = legacy_members
-
- for enum in ofinput.enums:
- for entry in enum.entries:
- identifiers.add_identifier(
- translation.loxi_name(entry.name),
- entry.name, enum.name, entry.value, wire_version,
- of_g.identifiers, of_g.identifiers_by_group)
-
-def populate_type_maps():
- """
- Use the type members in the IR to fill out the legacy type_maps.
- """
-
- def split_inherited_cls(cls):
- if cls == 'of_meter_band_stats': # HACK not a subtype of of_meter_band
- return None, None
- for parent in sorted(type_maps.inheritance_data.keys(), reverse=True):
- if cls.startswith(parent):
- return (parent, cls[len(parent)+1:])
- return None, None
-
- def find_experimenter(parent, cls):
- for experimenter in sorted(of_g.experimenter_name_to_id.keys(), reverse=True):
- prefix = parent + '_' + experimenter
- if cls.startswith(prefix) and cls != prefix:
- return experimenter
- return None
-
- def find_type_value(ofclass, m_name):
- for m in ofclass.members:
- if isinstance(m, OFTypeMember) and m.name == m_name:
- return m.value
- raise KeyError("ver=%d, cls=%s, m_name=%s" % (wire_version, cls, m_name))
-
- # Most inheritance classes: actions, instructions, etc
- for wire_version, protocol in of_g.ir.items():
- for ofclass in protocol.classes:
- cls = ofclass.name
- parent, subcls = split_inherited_cls(cls)
- if not (parent and subcls):
- continue
- if parent == 'of_oxm':
- type_len = find_type_value(ofclass, 'type_len')
- oxm_class = (type_len >> 16) & 0xffff
- if oxm_class != 0x8000:
- # Do not include experimenter OXMs in the main table
- val = type_maps.invalid_type
- else:
- val = (type_len >> 8) & 0xff
- else:
- val = find_type_value(ofclass, 'type')
- type_maps.inheritance_data[parent][wire_version][subcls] = val
-
- # Extensions (only actions for now)
- experimenter = find_experimenter(parent, cls)
- if parent == 'of_action' and experimenter:
- val = find_type_value(ofclass, 'subtype')
- type_maps.extension_action_subtype[wire_version][experimenter][cls] = val
- if wire_version >= of_g.VERSION_1_3:
- cls2 = parent + "_id" + cls[len(parent):]
- type_maps.extension_action_id_subtype[wire_version][experimenter][cls2] = val
-
- # Messages
- for wire_version, protocol in of_g.ir.items():
- for ofclass in protocol.classes:
- cls = ofclass.name
- # HACK (though this is what loxi_utils.class_is_message() does)
- if not [x for x in ofclass.members if isinstance(x, OFDataMember) and x.name == 'xid']:
- continue
- if type_maps.class_is_virtual(cls):
- continue
- subcls = cls[3:]
- val = find_type_value(ofclass, 'type')
- if not val in type_maps.message_types[wire_version].values():
- type_maps.message_types[wire_version][subcls] = val
-
- # Extensions
- experimenter = find_experimenter('of', cls)
- if experimenter and ofclass.superclass in ['of_bsn_header', 'of_nicira_header']:
- val = find_type_value(ofclass, 'subtype')
- type_maps.extension_message_subtype[wire_version][experimenter][cls] = val
-
- type_maps.generate_maps()
-
-def analyze_input():
- """
- Add information computed from the input, including offsets and
- lengths of struct members and the set of list and action_id types.
- """
-
- # Generate header classes for inheritance parents
- for wire_version, ordered_classes in of_g.ordered_classes.items():
- classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
- for cls in ordered_classes:
- if cls in type_maps.inheritance_map:
- new_cls = cls + '_header'
- of_g.ordered_classes[wire_version].append(new_cls)
- classes[new_cls] = classes[cls]
-
- # Generate action_id classes for OF 1.3
- for wire_version, ordered_classes in of_g.ordered_classes.items():
- if not wire_version in [of_g.VERSION_1_3]:
- continue
- classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
- for cls in ordered_classes:
- if not loxi_utils.class_is_action(cls):
- continue
- action = cls[10:]
- if action == '' or action == 'header':
- continue
- name = "of_action_id_" + action
- members = classes["of_action"][:]
- of_g.ordered_classes[wire_version].append(name)
- if type_maps.action_id_is_extension(name, wire_version):
- # Copy the base action classes thru subtype
- members = classes["of_action_" + action][:4]
- classes[name] = members
-
- # @fixme If we support extended actions in OF 1.3, need to add IDs
- # for them here
-
- for wire_version in of_g.wire_ver_map.keys():
- version_name = of_g.of_version_wire2name[wire_version]
- calculate_offsets_and_lengths(
- of_g.ordered_classes[wire_version],
- versions[version_name]['classes'],
- wire_version)
-
-def unify_input():
- """
- Create Unified View of Objects
- """
-
- global versions
-
- # Add classes to unified in wire-format order so that it is easier
- # to generate things later
- keys = versions.keys()
- keys.sort(reverse=True)
- for version in keys:
- wire_version = versions[version]["wire_version"]
- classes = versions[version]["classes"]
- for cls in of_g.ordered_classes[wire_version]:
- add_class(wire_version, cls, classes[cls])
-
-
-def log_all_class_info():
- """
- Log the results of processing the input
-
- Debug function
- """
-
- for cls in of_g.unified:
- for v in of_g.unified[cls]:
- if type(v) == type(0):
- log("cls: %s. ver: %d. base len %d. %s" %
- (str(cls), v, of_g.base_length[(cls, v)],
- loxi_utils.class_is_var_len(cls,v) and "not fixed"
- or "fixed"))
- if "use_version" in of_g.unified[cls][v]:
- log("cls %s: v %d mapped to %d" % (str(cls), v,
- of_g.unified[cls][v]["use_version"]))
- if "members" in of_g.unified[cls][v]:
- for member in of_g.unified[cls][v]["members"]:
- log(" %-20s: type %-20s. offset %3d" %
- (member["name"], member["m_type"],
- member["offset"]))
-
-def generate_all_files():
- """
- Create the files for the language target
- """
- lang_module.generate()
+################################################################
+#
+# Debug
+#
+################################################################
if __name__ == '__main__':
- of_g.loxigen_log_file = open("loxigen.log", "w")
- of_g.loxigen_dbg_file = sys.stdout
-
- of_g.process_commandline()
+ (options, args, target_versions) = cmdline.process_commandline()
# @fixme Use command line params to select log
- if not config_sanity_check():
+ logging.basicConfig(level = logging.INFO if not options.verbose else logging.DEBUG)
+
+ # Import the language file
+ lang_file = "lang_%s" % options.lang
+ lang_module = __import__(lang_file)
+
+ if hasattr(lang_module, "config_sanity_check") and not lang_module.config_sanity_check():
debug("Config sanity check failed\n")
sys.exit(1)
- # Import the language file
- lang_file = "lang_%s" % of_g.options.lang
- lang_module = __import__(lang_file)
-
# If list files, just list auto-gen files to stdout and exit
- if of_g.options.list_files:
+ if options.list_files:
for name in lang_module.targets:
- print of_g.options.install_dir + '/' + name
+ print options.install_dir + '/' + name
sys.exit(0)
- log("\nGenerating files for target language %s\n" % of_g.options.lang)
+ log("\nGenerating files for target language %s\n" % options.lang)
- initialize_versions()
- read_input()
- populate_type_maps()
- analyze_input()
- unify_input()
- order_and_assign_object_ids()
- log_all_class_info()
- generate_all_files()
+ loxi_globals.OFVersions.target_versions = target_versions
+ inputs = read_input()
+ build_ir(inputs)
+ #log_all_class_info()
+ lang_module.generate(options.install_dir)
diff --git a/openflow_input/standard-1.1 b/openflow_input/standard-1.1
index 9b72e8c..906519c 100644
--- a/openflow_input/standard-1.1
+++ b/openflow_input/standard-1.1
@@ -984,8 +984,44 @@
uint8_t type == 15;
uint16_t length;
uint32_t xid;
- uint16_t command;
- uint8_t group_type;
+ enum ofp_group_mod_command command == ?;
+ enum ofp_group_type group_type;
+ pad(1);
+ uint32_t group_id;
+ list(of_bucket_t) buckets;
+};
+
+struct of_group_add : of_group_mod {
+ uint8_t version;
+ uint8_t type == 15;
+ uint16_t length;
+ uint32_t xid;
+ enum ofp_group_mod_command command == 0;
+ enum ofp_group_type group_type;
+ pad(1);
+ uint32_t group_id;
+ list(of_bucket_t) buckets;
+};
+
+struct of_group_modify : of_group_mod {
+ uint8_t version;
+ uint8_t type == 15;
+ uint16_t length;
+ uint32_t xid;
+ enum ofp_group_mod_command command == 1;
+ enum ofp_group_type group_type;
+ pad(1);
+ uint32_t group_id;
+ list(of_bucket_t) buckets;
+};
+
+struct of_group_delete : of_group_mod {
+ uint8_t version;
+ uint8_t type == 15;
+ uint16_t length;
+ uint32_t xid;
+ enum ofp_group_mod_command command == 2;
+ enum ofp_group_type group_type;
pad(1);
uint32_t group_id;
list(of_bucket_t) buckets;
diff --git a/openflow_input/standard-1.2 b/openflow_input/standard-1.2
index e9d915a..90447f5 100644
--- a/openflow_input/standard-1.2
+++ b/openflow_input/standard-1.2
@@ -898,8 +898,44 @@
uint8_t type == 15;
uint16_t length;
uint32_t xid;
- uint16_t command;
- uint8_t group_type;
+ enum ofp_group_mod_command command == ?;
+ enum ofp_group_type group_type;
+ pad(1);
+ uint32_t group_id;
+ list(of_bucket_t) buckets;
+};
+
+struct of_group_add : of_group_mod {
+ uint8_t version;
+ uint8_t type == 15;
+ uint16_t length;
+ uint32_t xid;
+ enum ofp_group_mod_command command == 0;
+ enum ofp_group_type group_type;
+ pad(1);
+ uint32_t group_id;
+ list(of_bucket_t) buckets;
+};
+
+struct of_group_modify : of_group_mod {
+ uint8_t version;
+ uint8_t type == 15;
+ uint16_t length;
+ uint32_t xid;
+ enum ofp_group_mod_command command == 1;
+ enum ofp_group_type group_type;
+ pad(1);
+ uint32_t group_id;
+ list(of_bucket_t) buckets;
+};
+
+struct of_group_delete : of_group_mod {
+ uint8_t version;
+ uint8_t type == 15;
+ uint16_t length;
+ uint32_t xid;
+ enum ofp_group_mod_command command == 2;
+ enum ofp_group_type group_type;
pad(1);
uint32_t group_id;
list(of_bucket_t) buckets;
@@ -1078,7 +1114,7 @@
of_octets_t data;
};
-struct of_experimenter_error_msg {
+struct of_experimenter_error_msg : of_error_msg {
uint8_t version;
uint8_t type == 1;
uint16_t length;
diff --git a/openflow_input/standard-1.3 b/openflow_input/standard-1.3
index b4c4bee..4d0ca7e 100644
--- a/openflow_input/standard-1.3
+++ b/openflow_input/standard-1.3
@@ -1058,8 +1058,44 @@
uint8_t type == 15;
uint16_t length;
uint32_t xid;
- uint16_t command;
- uint8_t group_type;
+ enum ofp_group_mod_command command == ?;
+ enum ofp_group_type group_type;
+ pad(1);
+ uint32_t group_id;
+ list(of_bucket_t) buckets;
+};
+
+struct of_group_add : of_group_mod {
+ uint8_t version;
+ uint8_t type == 15;
+ uint16_t length;
+ uint32_t xid;
+ enum ofp_group_mod_command command == 0;
+ enum ofp_group_type group_type;
+ pad(1);
+ uint32_t group_id;
+ list(of_bucket_t) buckets;
+};
+
+struct of_group_modify : of_group_mod {
+ uint8_t version;
+ uint8_t type == 15;
+ uint16_t length;
+ uint32_t xid;
+ enum ofp_group_mod_command command == 1;
+ enum ofp_group_type group_type;
+ pad(1);
+ uint32_t group_id;
+ list(of_bucket_t) buckets;
+};
+
+struct of_group_delete : of_group_mod {
+ uint8_t version;
+ uint8_t type == 15;
+ uint16_t length;
+ uint32_t xid;
+ enum ofp_group_mod_command command == 2;
+ enum ofp_group_type group_type;
pad(1);
uint32_t group_id;
list(of_bucket_t) buckets;
@@ -1302,7 +1338,7 @@
of_octets_t data;
};
-struct of_experimenter_error_msg {
+struct of_experimenter_error_msg : of_error_msg {
uint8_t version;
uint8_t type == 1;
uint16_t length;
diff --git a/py_gen/codegen.py b/py_gen/codegen.py
index 70053f7..7cd068f 100644
--- a/py_gen/codegen.py
+++ b/py_gen/codegen.py
@@ -26,9 +26,9 @@
# under the EPL.
from collections import namedtuple
+import loxi_globals
import struct
-import of_g
-import loxi_front_end.type_maps as type_maps
+import template_utils
import loxi_utils.loxi_utils as utils
import util
import oftype
@@ -57,7 +57,7 @@
# HACK the oftype member attribute is replaced with an OFType instance
def build_ofclasses(version):
ofclasses = []
- for ofclass in of_g.ir[version].classes:
+ for ofclass in loxi_globals.ir[version].classes:
cls = ofclass.name
if ofclass.virtual:
continue
@@ -81,7 +81,10 @@
members.append(OFTypeMember(
name=m.name,
oftype=m.oftype,
- value=version))
+ value=version.wire_version,
+ base_length=m.base_length,
+ is_fixed_length=m.is_fixed_length,
+ offset=m.offset))
type_members.append(members[-1])
else:
members.append(m)
@@ -91,8 +94,8 @@
pyname=generate_pyname(cls),
members=members,
type_members=type_members,
- min_length=of_g.base_length[(cls, version)],
- is_fixed_length=(cls, version) in of_g.is_fixed_length,
+ min_length=ofclass.base_length,
+ is_fixed_length=ofclass.is_fixed_length,
has_internal_alignment=cls == 'of_action_set_field',
has_external_alignment=cls == 'of_match_v3'))
return ofclasses
@@ -122,7 +125,7 @@
def generate_const(out, name, version):
util.render_template(out, 'const.py', version=version,
- enums=of_g.ir[version].enums)
+ enums=loxi_globals.ir[version].enums)
def generate_instruction(out, name, version):
ofclasses = [x for x in ofclasses_by_version[version]
@@ -146,5 +149,5 @@
util.render_template(out, 'util.py', version=version)
def init():
- for version in of_g.supported_wire_protos:
+ for version in loxi_globals.OFVersions.target_versions:
ofclasses_by_version[version] = build_ofclasses(version)
diff --git a/py_gen/oftype.py b/py_gen/oftype.py
index b8f7de1..605d5be 100644
--- a/py_gen/oftype.py
+++ b/py_gen/oftype.py
@@ -28,7 +28,6 @@
from collections import namedtuple
import loxi_utils.loxi_utils as loxi_utils
-import of_g
OFTypeData = namedtuple("OFTypeData", ["init", "pack", "unpack"])
diff --git a/py_gen/templates/_pretty_print.py b/py_gen/templates/_pretty_print.py
index 74e6d79..c8c7770 100644
--- a/py_gen/templates/_pretty_print.py
+++ b/py_gen/templates/_pretty_print.py
@@ -24,6 +24,7 @@
:: # 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 loxi_globals import OFVersions
:: import loxi_utils.loxi_utils as loxi_utils
::
q.text("${ofclass.pyname} {")
@@ -49,7 +50,7 @@
q.text(util.pretty_mac(self.${m.name}))
:: elif m.oftype == 'of_ipv4_t':
q.text(util.pretty_ipv4(self.${m.name}))
-:: elif m.oftype == 'of_wc_bmap_t' and version in [1,2]:
+:: elif m.oftype == 'of_wc_bmap_t' and version in OFVersions.from_strings("1.0", "1.1"):
q.text(util.pretty_wildcards(self.${m.name}))
:: elif m.oftype == 'of_port_no_t':
q.text(util.pretty_port(self.${m.name}))
diff --git a/py_gen/templates/action.py b/py_gen/templates/action.py
index fcfd9a5..92c4e26 100644
--- a/py_gen/templates/action.py
+++ b/py_gen/templates/action.py
@@ -26,7 +26,7 @@
:: # under the EPL.
::
:: import itertools
-:: import of_g
+:: from loxi_globals import OFVersions
:: import py_gen.util as util
:: include('_copyright.py')
@@ -37,7 +37,7 @@
import util
import loxi.generic_util
import loxi
-:: if version >= 3:
+:: if version >= OFVersions.VERSION_1_2:
import oxm # for unpack
:: #endif
diff --git a/py_gen/templates/common.py b/py_gen/templates/common.py
index 1ab2f68..76ae631 100644
--- a/py_gen/templates/common.py
+++ b/py_gen/templates/common.py
@@ -26,23 +26,23 @@
:: # under the EPL.
::
:: include('_copyright.py')
-
+:: from loxi_globals import OFVersions
:: include('_autogen.py')
import sys
import struct
import action
-:: if version >= 2:
+:: if version >= OFVersions.VERSION_1_1:
import instruction # for unpack_list
:: #endif
-:: if version >= 4:
+:: if version >= OFVersions.VERSION_1_3:
import meter_band # for unpack_list
:: #endif
import const
import util
import loxi.generic_util
-:: if version >= 3:
+:: if version >= OFVersions.VERSION_1_2:
import oxm
:: #endif
@@ -94,13 +94,13 @@
:: #endfor
-:: if version == 1:
+:: if version == OFVersions.VERSION_1_0:
match = match_v1
-:: elif version == 2:
+:: elif version == OFVersions.VERSION_1_1:
match = match_v2
-:: elif version == 3:
+:: elif version == OFVersions.VERSION_1_2:
match = match_v3
-:: elif version == 4:
+:: elif version == OFVersions.VERSION_1_3:
:: # HACK
match = match_v3
:: #endif
diff --git a/py_gen/templates/const.py b/py_gen/templates/const.py
index 4567d56..d1c00f9 100644
--- a/py_gen/templates/const.py
+++ b/py_gen/templates/const.py
@@ -24,6 +24,7 @@
:: # 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 loxi_globals import OFVersions
::
:: blacklisted_map_groups = ['macro_definitions']
:: blacklisted_map_idents = ['OFPFW_NW_DST_BITS', 'OFPFW_NW_SRC_BITS',
@@ -34,12 +35,12 @@
:: include('_autogen.py')
-OFP_VERSION = ${version}
+OFP_VERSION = ${version.wire_version}
:: for enum in sorted(enums, key=lambda enum: enum.name):
# Identifiers from group ${enum.name}
:: for (ident, value) in enum.values:
-:: if version == 1 and ident.startswith('OFPP_'):
+:: if version == OFVersions.VERSION_1_0 and ident.startswith('OFPP_'):
:: # HACK loxi converts these to 32-bit
${ident} = ${"%#x" % (value & 0xffff)}
:: else:
@@ -52,7 +53,7 @@
:: for (ident, value) in enum.values:
:: if ident in blacklisted_map_idents:
:: pass
-:: elif version == 1 and ident.startswith('OFPP_'):
+:: elif version == OFVersions.VERSION_1_0 and ident.startswith('OFPP_'):
:: # HACK loxi converts these to 32-bit
${"%#x" % (value & 0xffff)}: ${repr(ident)},
:: else:
diff --git a/py_gen/templates/instruction.py b/py_gen/templates/instruction.py
index 88b0f89..978e38b 100644
--- a/py_gen/templates/instruction.py
+++ b/py_gen/templates/instruction.py
@@ -26,7 +26,6 @@
:: # under the EPL.
::
:: import itertools
-:: import of_g
:: import py_gen.util as util
:: include('_copyright.py')
diff --git a/py_gen/templates/message.py b/py_gen/templates/message.py
index e4c2c7b..39e9722 100644
--- a/py_gen/templates/message.py
+++ b/py_gen/templates/message.py
@@ -26,7 +26,8 @@
:: # under the EPL.
::
:: import itertools
-:: import of_g
+:: from loxi_globals import OFVersions
+:: import loxi_globals
:: import py_gen.util as util
:: import py_gen.oftype
:: include('_copyright.py')
@@ -38,10 +39,10 @@
import const
import common
import action # for unpack_list
-:: if version >= 2:
+:: if version >= OFVersions.VERSION_1_1:
import instruction # for unpack_list
:: #endif
-:: if version >= 4:
+:: if version >= OFVersions.VERSION_1_3:
import meter_band # for unpack_list
:: #endif
import util
@@ -132,9 +133,9 @@
raise loxi.ProtocolError("unexpected error type %u" % err_type)
def parse_flow_mod(buf):
-:: if version == 1:
+:: if version == OFVersions.VERSION_1_0:
:: offset = 57
-:: elif version >= 2:
+:: elif version >= OFVersions.VERSION_1_1:
:: offset = 25
:: #endif
if len(buf) < ${offset} + 1:
@@ -146,6 +147,18 @@
else:
raise loxi.ProtocolError("unexpected flow mod cmd %u" % cmd)
+:: if version >= OFVersions.VERSION_1_0:
+def parse_group_mod(buf):
+:: offset = 8
+ if len(buf) < ${offset} + 2:
+ raise loxi.ProtocolError("message too short")
+ cmd, = struct.unpack_from("!H", buf, ${offset})
+ if cmd in flow_mod_parsers:
+ return group_mod_parsers[cmd](buf)
+ else:
+ raise loxi.ProtocolError("unexpected group mod cmd %u" % cmd)
+:: #endif
+
def parse_stats_reply(buf):
if len(buf) < 8 + 2:
raise loxi.ProtocolError("message too short")
@@ -226,18 +239,18 @@
const.OFPET_FLOW_MOD_FAILED : flow_mod_failed_error_msg.unpack,
const.OFPET_PORT_MOD_FAILED : port_mod_failed_error_msg.unpack,
const.OFPET_QUEUE_OP_FAILED : queue_op_failed_error_msg.unpack,
-:: if version >= of_g.VERSION_1_1:
+:: if version >= OFVersions.VERSION_1_1:
const.OFPET_BAD_INSTRUCTION : bad_instruction_error_msg.unpack,
const.OFPET_BAD_MATCH : bad_match_error_msg.unpack,
const.OFPET_GROUP_MOD_FAILED : group_mod_failed_error_msg.unpack,
const.OFPET_TABLE_MOD_FAILED : table_mod_failed_error_msg.unpack,
const.OFPET_SWITCH_CONFIG_FAILED : switch_config_failed_error_msg.unpack,
:: #endif
-:: if version >= of_g.VERSION_1_2:
+:: if version >= OFVersions.VERSION_1_2:
const.OFPET_ROLE_REQUEST_FAILED : role_request_failed_error_msg.unpack,
const.OFPET_EXPERIMENTER : experimenter_error_msg.unpack,
:: #endif
-:: if version >= of_g.VERSION_1_3:
+:: if version >= OFVersions.VERSION_1_3:
const.OFPET_METER_MOD_FAILED : meter_mod_failed_error_msg.unpack,
const.OFPET_TABLE_FEATURES_FAILED : table_features_failed_error_msg.unpack,
:: #endif
@@ -251,6 +264,14 @@
const.OFPFC_DELETE_STRICT : flow_delete_strict.unpack,
}
+:: if version >= OFVersions.VERSION_1_1:
+group_mod_parsers = {
+ const.OFPGC_ADD : group_add.unpack,
+ const.OFPGC_MODIFY : group_modify.unpack,
+ const.OFPGC_DELETE : group_delete.unpack,
+}
+:: #endif
+
stats_reply_parsers = {
const.OFPST_DESC : desc_stats_reply.unpack,
const.OFPST_FLOW : flow_stats_reply.unpack,
@@ -259,14 +280,14 @@
const.OFPST_PORT : port_stats_reply.unpack,
const.OFPST_QUEUE : queue_stats_reply.unpack,
const.OFPST_EXPERIMENTER : parse_experimenter_stats_reply,
-:: if version >= of_g.VERSION_1_1:
+:: if version >= OFVersions.VERSION_1_1:
const.OFPST_GROUP : group_stats_reply.unpack,
const.OFPST_GROUP_DESC : group_desc_stats_reply.unpack,
:: #endif
-:: if version >= of_g.VERSION_1_2:
+:: if version >= OFVersions.VERSION_1_2:
const.OFPST_GROUP_FEATURES : group_features_stats_reply.unpack,
:: #endif
-:: if version >= of_g.VERSION_1_3:
+:: if version >= OFVersions.VERSION_1_3:
const.OFPST_METER : meter_stats_reply.unpack,
const.OFPST_METER_CONFIG : meter_config_stats_reply.unpack,
const.OFPST_METER_FEATURES : meter_features_stats_reply.unpack,
@@ -283,14 +304,14 @@
const.OFPST_PORT : port_stats_request.unpack,
const.OFPST_QUEUE : queue_stats_request.unpack,
const.OFPST_EXPERIMENTER : parse_experimenter_stats_request,
-:: if version >= of_g.VERSION_1_1:
+:: if version >= OFVersions.VERSION_1_1:
const.OFPST_GROUP : group_stats_request.unpack,
const.OFPST_GROUP_DESC : group_desc_stats_request.unpack,
:: #endif
-:: if version >= of_g.VERSION_1_2:
+:: if version >= OFVersions.VERSION_1_2:
const.OFPST_GROUP_FEATURES : group_features_stats_request.unpack,
:: #endif
-:: if version >= of_g.VERSION_1_3:
+:: if version >= OFVersions.VERSION_1_3:
const.OFPST_METER : meter_stats_request.unpack,
const.OFPST_METER_CONFIG : meter_config_stats_request.unpack,
const.OFPST_METER_FEATURES : meter_features_stats_request.unpack,
@@ -315,7 +336,7 @@
experimenter_stats_request_parsers = {
0x005c16c7: {
-:: if version >= of_g.VERSION_1_3:
+:: if version >= OFVersions.VERSION_1_3:
1: bsn_lacp_stats_request.unpack,
:: #endif
},
@@ -323,7 +344,7 @@
experimenter_stats_reply_parsers = {
0x005c16c7: {
-:: if version >= of_g.VERSION_1_3:
+:: if version >= OFVersions.VERSION_1_3:
1: bsn_lacp_stats_reply.unpack,
:: #endif
},
diff --git a/py_gen/templates/meter_band.py b/py_gen/templates/meter_band.py
index eeb9ff2..37cc6d9 100644
--- a/py_gen/templates/meter_band.py
+++ b/py_gen/templates/meter_band.py
@@ -26,7 +26,6 @@
:: # under the EPL.
::
:: import itertools
-:: import of_g
:: import py_gen.util as util
:: include('_copyright.py')
diff --git a/py_gen/templates/oxm.py b/py_gen/templates/oxm.py
index 50eb4bd..ecb52ac 100644
--- a/py_gen/templates/oxm.py
+++ b/py_gen/templates/oxm.py
@@ -26,7 +26,6 @@
:: # under the EPL.
::
:: import itertools
-:: import of_g
:: import py_gen.oftype
:: include('_copyright.py')
diff --git a/py_gen/templates/toplevel_init.py b/py_gen/templates/toplevel_init.py
index b0ee86d..b170bd8 100644
--- a/py_gen/templates/toplevel_init.py
+++ b/py_gen/templates/toplevel_init.py
@@ -25,12 +25,15 @@
:: # EPL for the specific language governing permissions and limitations
:: # under the EPL.
::
-:: import of_g
:: include('_copyright.py')
-
+:: import loxi_globals
:: include('_autogen.py')
-version_names = ${repr(of_g.param_version_names)}
+version_names = {
+:: for v in loxi_globals.OFVersions.all_supported:
+ ${v.wire_version}: "${v.version}",
+:: #endfor
+}
def protocol(ver):
"""
diff --git a/py_gen/templates/util.py b/py_gen/templates/util.py
index 1566e82..50a64db 100644
--- a/py_gen/templates/util.py
+++ b/py_gen/templates/util.py
@@ -26,7 +26,7 @@
:: # under the EPL.
::
:: include('_copyright.py')
-
+:: from loxi_globals import OFVersions
:: include('_autogen.py')
import loxi
@@ -52,7 +52,7 @@
set_flags.append("%#x" % v)
return '|'.join(set_flags) or '0'
-:: if version in [1,2]:
+:: if version in (OFVersions.VERSION_1_0, OFVersions.VERSION_1_1):
def pretty_wildcards(v):
if v == const.OFPFW_ALL:
return 'OFPFW_ALL'
@@ -71,70 +71,70 @@
return v
def pack_port_no(value):
-:: if version == 1:
+:: if version == OFVersions.VERSION_1_0:
return struct.pack("!H", value)
:: else:
return struct.pack("!L", value)
:: #endif
def unpack_port_no(reader):
-:: if version == 1:
+:: if version == OFVersions.VERSION_1_0:
return reader.read("!H")[0]
:: else:
return reader.read("!L")[0]
:: #endif
def pack_fm_cmd(value):
-:: if version == 1:
+:: if version == OFVersions.VERSION_1_0:
return struct.pack("!H", value)
:: else:
return struct.pack("!B", value)
:: #endif
def unpack_fm_cmd(reader):
-:: if version == 1:
+:: if version == OFVersions.VERSION_1_0:
return reader.read("!H")[0]
:: else:
return reader.read("!B")[0]
:: #endif
def init_wc_bmap():
-:: if version <= 2:
+:: if version <= OFVersions.VERSION_1_1:
return const.OFPFW_ALL
:: else:
return 0
:: #endif
def pack_wc_bmap(value):
-:: if version <= 2:
+:: if version <= OFVersions.VERSION_1_1:
return struct.pack("!L", value)
:: else:
return struct.pack("!Q", value)
:: #endif
def unpack_wc_bmap(reader):
-:: if version <= 2:
+:: if version <= OFVersions.VERSION_1_1:
return reader.read("!L")[0]
:: else:
return reader.read("!Q")[0]
:: #endif
def init_match_bmap():
-:: if version <= 2:
+:: if version <= OFVersions.VERSION_1_1:
return const.OFPFW_ALL
:: else:
return 0
:: #endif
def pack_match_bmap(value):
-:: if version <= 2:
+:: if version <= OFVersions.VERSION_1_1:
return struct.pack("!L", value)
:: else:
return struct.pack("!Q", value)
:: #endif
def unpack_match_bmap(reader):
-:: if version <= 2:
+:: if version <= OFVersions.VERSION_1_1:
return reader.read("!L")[0]
:: else:
return reader.read("!Q")[0]
diff --git a/py_gen/util.py b/py_gen/util.py
index f527964..b405441 100644
--- a/py_gen/util.py
+++ b/py_gen/util.py
@@ -30,19 +30,20 @@
"""
import os
-import of_g
+import loxi_globals
+import template_utils
import loxi_utils.loxi_utils as utils
templates_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'templates')
def render_template(out, name, **context):
- utils.render_template(out, name, [templates_dir], context)
+ template_utils.render_template(out, name, [templates_dir], context)
def render_static(out, name):
- utils.render_static(out, name, [templates_dir])
+ template_utils.render_static(out, name, [templates_dir])
def constant_for_value(version, group, value):
- enums = of_g.ir[version].enums
+ enums = loxi_globals.ir[version].enums
enum = [x for x in enums if x.name == group][0]
for name, value2 in enum.values:
if value == value2:
diff --git a/template_utils.py b/template_utils.py
new file mode 100644
index 0000000..1db0292
--- /dev/null
+++ b/template_utils.py
@@ -0,0 +1,90 @@
+# 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 os
+import sys
+
+import tenjin
+
+""" @brief utilities for rendering templates
+"""
+
+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())
+
+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(install_dir, name):
+ """
+ Open an output file for writing
+
+ 'name' may include slashes. Subdirectories will be automatically created.
+ """
+ print "Writing %s" % name
+ path = os.path.join(install_dir, name)
+ dirpath = os.path.dirname(path)
+ if not os.path.exists(dirpath):
+ os.makedirs(dirpath)
+ return open(path, "w")
diff --git a/test_data/of13/group_mod.data b/test_data/of13/group_modify.data
similarity index 65%
rename from test_data/of13/group_mod.data
rename to test_data/of13/group_modify.data
index 8f25b57..87eee29 100644
--- a/test_data/of13/group_mod.data
+++ b/test_data/of13/group_modify.data
@@ -37,9 +37,8 @@
00 00 # buckets[1].actions[1].max_len
00 00 00 00 00 00 # pad
-- python
-ofp.message.group_mod(
+ofp.message.group_modify(
xid=0x12345678,
- command=ofp.OFPGC_MODIFY,
group_type=ofp.OFPGT_FF,
group_id=5,
buckets=[
@@ -57,3 +56,31 @@
actions=[
ofp.action.output(port=5, max_len=0),
ofp.action.output(port=6, max_len=0)])])
+-- java
+ OFActions actions = factory.actions();
+ builder
+ .setXid(0x12345678)
+ .setGroupType(OFGroupType.FF)
+ .setGroup(OFGroup.of(5))
+ .setBuckets(ImmutableList.<OFBucket>of(
+ factory.buildBucket()
+ .setWeight(1)
+ .setWatchPort(OFPort.of(5))
+ .setWatchGroup(OFGroup.ANY)
+ .setActions(ImmutableList.<OFAction>of(
+ actions.output(OFPort.of(5), 0),
+ actions.output(OFPort.of(6), 0)
+ ))
+ .build(),
+ factory.buildBucket()
+ .setWeight(1)
+ .setWatchPort(OFPort.of(6))
+ .setWatchGroup(OFGroup.ANY)
+ .setActions(ImmutableList.<OFAction>of(
+ actions.output(OFPort.of(5), 0),
+ actions.output(OFPort.of(6), 0)
+ ))
+ .build()
+ )
+ )
+ .build();
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 19cde4e..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("test", 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("test", ast)
+ ofinput = frontend.create_ofinput("standard-1.0", ast)
expected_classes = [
OFClass(name='of_queue_prop', superclass=None, members=[
OFDiscriminatorMember('type', 'uint16_t'),