Initial import
LoxiGen is the work of several developers, not just myself.
diff --git a/py_gen/__init__.py b/py_gen/__init__.py
new file mode 100644
index 0000000..5e4e379
--- /dev/null
+++ b/py_gen/__init__.py
@@ -0,0 +1,26 @@
+# 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.
diff --git a/py_gen/codegen.py b/py_gen/codegen.py
new file mode 100644
index 0000000..211c71e
--- /dev/null
+++ b/py_gen/codegen.py
@@ -0,0 +1,164 @@
+# 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 collections import namedtuple
+import of_g
+import loxi_front_end.type_maps as type_maps
+import loxi_utils.loxi_utils as utils
+import util
+import oftype
+
+OFClass = namedtuple('OFClass', ['name', 'pyname',
+ 'members', 'length_member', 'type_members',
+ 'min_length', 'is_fixed_length'])
+Member = namedtuple('Member', ['name', 'oftype', 'offset', 'skip'])
+LengthMember = namedtuple('LengthMember', ['name', 'oftype', 'offset'])
+TypeMember = namedtuple('TypeMember', ['name', 'oftype', 'offset', 'value'])
+
+def get_type_values(cls, version):
+ """
+ Returns a map from the name of the type member to its value.
+ """
+ type_values = {}
+
+ # Primary wire type
+ if utils.class_is_message(cls):
+ type_values['version'] = 'const.OFP_VERSION'
+ type_values['type'] = util.constant_for_value(version, "ofp_type", util.primary_wire_type(cls, version))
+ if cls in type_maps.flow_mod_list:
+ type_values['_command'] = util.constant_for_value(version, "ofp_flow_mod_command",
+ type_maps.flow_mod_types[version][cls[8:]])
+ if cls in type_maps.stats_request_list:
+ type_values['stats_type'] = util.constant_for_value(version, "ofp_stats_types",
+ type_maps.stats_types[version][cls[3:-14]])
+ if cls in type_maps.stats_reply_list:
+ type_values['stats_type'] = util.constant_for_value(version, "ofp_stats_types",
+ type_maps.stats_types[version][cls[3:-12]])
+ if type_maps.message_is_extension(cls, version):
+ type_values['experimenter'] = '%#x' % type_maps.extension_to_experimenter_id(cls)
+ type_values['subtype'] = type_maps.extension_message_to_subtype(cls, version)
+ elif utils.class_is_action(cls):
+ type_values['type'] = util.constant_for_value(version, "ofp_action_type", util.primary_wire_type(cls, version))
+ if type_maps.action_is_extension(cls, version):
+ type_values['experimenter'] = '%#x' % type_maps.extension_to_experimenter_id(cls)
+ type_values['subtype'] = type_maps.extension_action_to_subtype(cls, version)
+ elif utils.class_is_queue_prop(cls):
+ type_values['type'] = util.constant_for_value(version, "ofp_queue_properties", util.primary_wire_type(cls, version))
+
+ return type_values
+
+# Create intermediate representation
+def build_ofclasses(version):
+ blacklist = ["of_action", "of_action_header", "of_header", "of_queue_prop",
+ "of_queue_prop_header", "of_experimenter", "of_action_experimenter"]
+ ofclasses = []
+ for cls in of_g.standard_class_order:
+ if version not in of_g.unified[cls] or cls in blacklist:
+ continue
+ unified_class = util.lookup_unified_class(cls, version)
+
+ # Name for the generated Python class
+ if utils.class_is_action(cls):
+ pyname = cls[10:]
+ else:
+ pyname = cls[3:]
+
+ type_values = get_type_values(cls, version)
+ members = []
+
+ length_member = None
+ type_members = []
+
+ for member in unified_class['members']:
+ if member['name'] in ['length', 'len']:
+ length_member = LengthMember(name=member['name'],
+ offset=member['offset'],
+ oftype=oftype.OFType(member['m_type'], version))
+ elif member['name'] in type_values:
+ type_members.append(TypeMember(name=member['name'],
+ offset=member['offset'],
+ oftype=oftype.OFType(member['m_type'], version),
+ value=type_values[member['name']]))
+ else:
+ # HACK ensure member names are unique
+ if member['name'] == "pad" and \
+ [x for x in members if x.name == 'pad']:
+ m_name = "pad2"
+ else:
+ m_name = member['name']
+ members.append(Member(name=m_name,
+ oftype=oftype.OFType(member['m_type'], version),
+ offset=member['offset'],
+ skip=member['name'] in of_g.skip_members))
+
+ ofclasses.append(
+ OFClass(name=cls,
+ pyname=pyname,
+ members=members,
+ length_member=length_member,
+ type_members=type_members,
+ min_length=of_g.base_length[(cls, version)],
+ is_fixed_length=(cls, version) in of_g.is_fixed_length))
+ return ofclasses
+
+def generate_init(out, name, version):
+ util.render_template(out, 'init.py')
+
+def generate_action(out, name, version):
+ ofclasses = [x for x in build_ofclasses(version)
+ if utils.class_is_action(x.name)]
+ util.render_template(out, 'action.py', ofclasses=ofclasses)
+
+def generate_common(out, name, version):
+ ofclasses = [x for x in build_ofclasses(version)
+ if not utils.class_is_message(x.name)
+ and not utils.class_is_action(x.name)
+ and not utils.class_is_list(x.name)]
+ util.render_template(out, 'common.py', ofclasses=ofclasses)
+
+def generate_const(out, name, version):
+ groups = {}
+ for (group, idents) in of_g.identifiers_by_group.items():
+ items = []
+ for ident in idents:
+ info = of_g.identifiers[ident]
+ if version in info["values_by_version"]:
+ items.append((info["ofp_name"], info["values_by_version"][version]))
+ if items:
+ groups[group] = items
+ util.render_template(out, 'const.py', version=version, groups=groups)
+
+def generate_message(out, name, version):
+ ofclasses = [x for x in build_ofclasses(version)
+ if utils.class_is_message(x.name)]
+ util.render_template(out, 'message.py', ofclasses=ofclasses, version=version)
+
+def generate_pp(out, name, version):
+ util.render_template(out, 'pp.py')
+
+def generate_util(out, name, version):
+ util.render_template(out, 'util.py')
diff --git a/py_gen/oftype.py b/py_gen/oftype.py
new file mode 100644
index 0000000..ee5040c
--- /dev/null
+++ b/py_gen/oftype.py
@@ -0,0 +1,180 @@
+# 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 of_g
+import loxi_utils.loxi_utils as utils
+import unittest
+
+class OFType(object):
+ """
+ Encapsulates knowledge about the OpenFlow type system.
+ """
+
+ version = None
+ base = None
+ is_array = False
+ array_length = None
+
+ def __init__(self, string, version):
+ self.version = version
+ self.array_length, self.base = utils.type_dec_to_count_base(string)
+ self.is_array = self.array_length != 1
+
+ def gen_init_expr(self):
+ if utils.class_is_list(self.base):
+ v = "[]"
+ elif self.base.find("uint") == 0 or self.base in ["char", "of_port_no_t"]:
+ v = "0"
+ elif self.base == 'of_mac_addr_t':
+ v = '[0,0,0,0,0,0]'
+ elif self.base == 'of_wc_bmap_t':
+ v = 'const.OFPFW_ALL'
+ elif self.base in ['of_octets_t', 'of_port_name_t', 'of_table_name_t',
+ 'of_desc_str_t', 'of_serial_num_t']:
+ v = '""'
+ elif self.base == 'of_match_t':
+ v = 'common.match()'
+ elif self.base == 'of_port_desc_t':
+ v = 'common.port_desc()'
+ else:
+ v = "None"
+
+ if self.is_array:
+ return "[" + ','.join([v] * self.array_length) + "]"
+ else:
+ return v
+
+ def gen_pack_expr(self, expr_expr):
+ pack_fmt = self._pack_fmt()
+ if pack_fmt and not self.is_array:
+ return 'struct.pack("!%s", %s)' % (pack_fmt, expr_expr)
+ elif pack_fmt and self.is_array:
+ return 'struct.pack("!%s%s", *%s)' % (self.array_length, pack_fmt, expr_expr)
+ elif self.base == 'of_octets_t':
+ return expr_expr
+ elif utils.class_is_list(self.base):
+ return '"".join([x.pack() for x in %s])' % expr_expr
+ elif self.base == 'of_mac_addr_t':
+ return 'struct.pack("!6B", *%s)' % expr_expr
+ elif self.base in ['of_match_t', 'of_port_desc_t']:
+ return '%s.pack()' % expr_expr
+ elif self.base == 'of_port_name_t':
+ return self._gen_string_pack_expr(16, expr_expr)
+ elif self.base == 'of_table_name_t' or self.base == 'of_serial_num_t':
+ return self._gen_string_pack_expr(32, expr_expr)
+ elif self.base == 'of_desc_str_t':
+ return self._gen_string_pack_expr(256, expr_expr)
+ else:
+ return "'TODO pack %s'" % self.base
+
+ def _gen_string_pack_expr(self, length, expr_expr):
+ return 'struct.pack("!%ds", %s)' % (length, expr_expr)
+
+ def gen_unpack_expr(self, buf_expr, offset_expr):
+ pack_fmt = self._pack_fmt()
+ if pack_fmt and not self.is_array:
+ return "struct.unpack_from('!%s', %s, %s)[0]" % (pack_fmt, buf_expr, offset_expr)
+ elif pack_fmt and self.is_array:
+ return "list(struct.unpack_from('!%d%s', %s, %s))" % (self.array_length, pack_fmt, buf_expr, offset_expr)
+ elif self.base == 'of_octets_t':
+ return "%s[%s:]" % (buf_expr, offset_expr)
+ elif self.base == 'of_mac_addr_t':
+ return "list(struct.unpack_from('!6B', %s, %s))" % (buf_expr, offset_expr)
+ elif self.base == 'of_match_t':
+ return 'common.match.unpack(buffer(%s, %s))' % (buf_expr, offset_expr)
+ elif self.base == 'of_port_desc_t':
+ return 'common.port_desc.unpack(buffer(%s, %s))' % (buf_expr, offset_expr)
+ elif self.base == 'of_list_action_t':
+ return 'action.unpack_list(buffer(%s, %s))' % (buf_expr, offset_expr)
+ elif self.base == 'of_list_flow_stats_entry_t':
+ return 'common.unpack_list_flow_stats_entry(buffer(%s, %s))' % (buf_expr, offset_expr)
+ elif self.base == 'of_list_queue_prop_t':
+ return 'common.unpack_list_queue_prop(buffer(%s, %s))' % (buf_expr, offset_expr)
+ elif self.base == 'of_list_packet_queue_t':
+ return 'common.unpack_list_packet_queue(buffer(%s, %s))' % (buf_expr, offset_expr)
+ elif self.base == 'of_port_name_t':
+ return self._gen_string_unpack_expr(16, buf_expr, offset_expr)
+ elif self.base == 'of_table_name_t' or self.base == 'of_serial_num_t':
+ return self._gen_string_unpack_expr(32, buf_expr, offset_expr)
+ elif self.base == 'of_desc_str_t':
+ return self._gen_string_unpack_expr(256, buf_expr, offset_expr)
+ elif utils.class_is_list(self.base):
+ element_cls = utils.list_to_entry_type(self.base)[:-2]
+ if ((element_cls, self.version) in of_g.is_fixed_length):
+ klass_name = self.base[8:-2]
+ element_size, = of_g.base_length[(element_cls, self.version)],
+ return 'util.unpack_array(common.%s.unpack, %d, buffer(%s, %s))' % (klass_name, element_size, buf_expr, offset_expr)
+ else:
+ return "None # TODO unpack list %s" % self.base
+ else:
+ return "None # TODO unpack %s" % self.base
+
+ def _gen_string_unpack_expr(self, length, buf_expr, offset_expr):
+ return 'str(buffer(%s, %s, %d)).rstrip("\\x00")' % (buf_expr, offset_expr, length)
+
+ def _pack_fmt(self):
+ if self.base == "char":
+ return "B"
+ if self.base == "uint8_t":
+ return "B"
+ if self.base == "uint16_t":
+ return "H"
+ if self.base == "uint32_t":
+ return "L"
+ if self.base == "uint64_t":
+ return "Q"
+ if self.base == "of_port_no_t":
+ if self.version == of_g.VERSION_1_0:
+ return "H"
+ else:
+ return "L"
+ if self.base == "of_fm_cmd_t":
+ if self.version == of_g.VERSION_1_0:
+ return "H"
+ else:
+ return "B"
+ if self.base in ["of_wc_bmap_t", "of_match_bmap_t"]:
+ if self.version in [of_g.VERSION_1_0, of_g.VERSION_1_1]:
+ return "L"
+ else:
+ return "Q"
+ return None
+
+class TestOFType(unittest.TestCase):
+ def test_init(self):
+ from oftype import OFType
+ self.assertEquals("None", OFType("of_list_action_t", 1).gen_init_expr())
+ self.assertEquals("[0,0,0]", OFType("uint32_t[3]", 1).gen_init_expr())
+
+ def test_pack(self):
+ self.assertEquals('struct.pack("!16s", "foo")', OFType("of_port_name_t", 1).gen_pack_expr('"foo"'))
+
+ def test_unpack(self):
+ self.assertEquals('str(buffer(buf, 8, 16)).rstrip("\\x00")', OFType("of_port_name_t", 1).gen_unpack_expr('buf', 8))
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/py_gen/templates/.gitignore b/py_gen/templates/.gitignore
new file mode 100644
index 0000000..81fc9b5
--- /dev/null
+++ b/py_gen/templates/.gitignore
@@ -0,0 +1,2 @@
+# Tenjin cache files
+/*.cache
diff --git a/py_gen/templates/_autogen.py b/py_gen/templates/_autogen.py
new file mode 100644
index 0000000..520ad9e
--- /dev/null
+++ b/py_gen/templates/_autogen.py
@@ -0,0 +1,30 @@
+:: # 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 inspect, os
+# Automatically generated by LOXI from template #{os.path.basename(inspect.stack()[3][1])}
+# Do not modify
diff --git a/py_gen/templates/_copyright.py b/py_gen/templates/_copyright.py
new file mode 100644
index 0000000..24dc1b6
--- /dev/null
+++ b/py_gen/templates/_copyright.py
@@ -0,0 +1,30 @@
+:: # 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.
+::
+# Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior University
+# Copyright (c) 2011, 2012 Open Networking Foundation
+# Copyright (c) 2012, 2013 Big Switch Networks, Inc.
diff --git a/py_gen/templates/_pack.py b/py_gen/templates/_pack.py
new file mode 100644
index 0000000..0d50a38
--- /dev/null
+++ b/py_gen/templates/_pack.py
@@ -0,0 +1,47 @@
+:: # 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.
+::
+:: # TODO coalesce format strings
+:: all_members = ofclass.members[:]
+:: if ofclass.length_member: all_members.append(ofclass.length_member)
+:: all_members.extend(ofclass.type_members)
+:: all_members.sort(key=lambda x: x.offset)
+:: length_member_index = None
+:: index = 0
+:: for m in all_members:
+:: if m == ofclass.length_member:
+:: length_member_index = index
+ packed.append(${m.oftype.gen_pack_expr('0')}) # placeholder for ${m.name} at index ${length_member_index}
+:: else:
+ packed.append(${m.oftype.gen_pack_expr('self.' + m.name)})
+:: #endif
+:: index += 1
+:: #endfor
+:: if length_member_index != None:
+ length = sum([len(x) for x in packed])
+ packed[${length_member_index}] = ${ofclass.length_member.oftype.gen_pack_expr('length')}
+:: #endif
diff --git a/py_gen/templates/_pack_packet_out.py b/py_gen/templates/_pack_packet_out.py
new file mode 100644
index 0000000..ad8b827
--- /dev/null
+++ b/py_gen/templates/_pack_packet_out.py
@@ -0,0 +1,39 @@
+:: # 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.
+::
+ packed.append(struct.pack("!B", self.version))
+ packed.append(struct.pack("!B", self.type))
+ packed.append(struct.pack("!H", 0)) # placeholder for length at index 3
+ packed.append(struct.pack("!L", self.xid))
+ packed.append(struct.pack("!L", self.buffer_id))
+ packed.append(struct.pack("!H", self.in_port))
+ packed_actions = "".join([x.pack() for x in self.actions])
+ packed.append(struct.pack("!H", len(packed_actions)))
+ packed.append(packed_actions)
+ packed.append(self.data)
+ length = sum([len(x) for x in packed])
+ packed[2] = struct.pack("!H", length)
diff --git a/py_gen/templates/_pretty_print.py b/py_gen/templates/_pretty_print.py
new file mode 100644
index 0000000..604cd94
--- /dev/null
+++ b/py_gen/templates/_pretty_print.py
@@ -0,0 +1,61 @@
+:: # 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.
+::
+ q.text("${ofclass.pyname} {")
+ with q.group():
+ with q.indent(2):
+ q.breakable()
+:: first = True
+:: for m in ofclass.members:
+:: if m.name == 'actions_len': continue
+:: if not first:
+ q.text(","); q.breakable()
+:: else:
+:: first = False
+:: #endif
+ q.text("${m.name} = ");
+:: if m.name == "xid":
+ if self.${m.name} != None:
+ q.text("%#x" % self.${m.name})
+ else:
+ q.text('None')
+:: elif m.oftype.base == 'of_mac_addr_t':
+ q.text(util.pretty_mac(self.${m.name}))
+:: elif m.oftype.base == 'uint32_t' and m.name.startswith("ipv4"):
+ q.text(util.pretty_ipv4(self.${m.name}))
+:: elif m.oftype.base == 'of_wc_bmap_t':
+ q.text(util.pretty_wildcards(self.${m.name}))
+:: elif m.oftype.base == 'of_port_no_t':
+ q.text(util.pretty_port(self.${m.name}))
+:: elif m.oftype.base.startswith("uint") and not m.oftype.is_array:
+ q.text("%#x" % self.${m.name})
+:: else:
+ q.pp(self.${m.name})
+:: #endif
+:: #endfor
+ q.breakable()
+ q.text('}')
diff --git a/py_gen/templates/_unpack.py b/py_gen/templates/_unpack.py
new file mode 100644
index 0000000..173ebb5
--- /dev/null
+++ b/py_gen/templates/_unpack.py
@@ -0,0 +1,49 @@
+:: # 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.
+::
+:: # TODO coalesce format strings
+:: all_members = ofclass.members[:]
+:: if ofclass.length_member: all_members.append(ofclass.length_member)
+:: all_members.extend(ofclass.type_members)
+:: all_members.sort(key=lambda x: x.offset)
+:: for m in all_members:
+:: unpack_expr = m.oftype.gen_unpack_expr('buf', m.offset)
+:: if m == ofclass.length_member:
+ _length = ${unpack_expr}
+ assert(_length == len(buf))
+:: if ofclass.is_fixed_length:
+ if _length != ${ofclass.min_length}: raise loxi.ProtocolError("${ofclass.pyname} length is %d, should be ${ofclass.min_length}" % _length)
+:: else:
+ if _length < ${ofclass.min_length}: raise loxi.ProtocolError("${ofclass.pyname} length is %d, should be at least ${ofclass.min_length}" % _length)
+:: #endif
+:: elif m in ofclass.type_members:
+ ${m.name} = ${unpack_expr}
+ assert(${m.name} == ${m.value})
+:: else:
+ obj.${m.name} = ${unpack_expr}
+:: #endif
+:: #endfor
diff --git a/py_gen/templates/_unpack_packet_out.py b/py_gen/templates/_unpack_packet_out.py
new file mode 100644
index 0000000..b97e829
--- /dev/null
+++ b/py_gen/templates/_unpack_packet_out.py
@@ -0,0 +1,40 @@
+:: # 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.
+::
+ version = struct.unpack_from('!B', buf, 0)[0]
+ assert(version == const.OFP_VERSION)
+ type = struct.unpack_from('!B', buf, 1)[0]
+ assert(type == const.OFPT_PACKET_OUT)
+ _length = struct.unpack_from('!H', buf, 2)[0]
+ assert(_length == len(buf))
+ if _length < 16: raise loxi.ProtocolError("packet_out length is %d, should be at least 16" % _length)
+ obj.xid = struct.unpack_from('!L', buf, 4)[0]
+ obj.buffer_id = struct.unpack_from('!L', buf, 8)[0]
+ obj.in_port = struct.unpack_from('!H', buf, 12)[0]
+ actions_len = struct.unpack_from('!H', buf, 14)[0]
+ obj.actions = action.unpack_list(buffer(buf, 16, actions_len))
+ obj.data = str(buffer(buf, 16+actions_len))
diff --git a/py_gen/templates/action.py b/py_gen/templates/action.py
new file mode 100644
index 0000000..439e56f
--- /dev/null
+++ b/py_gen/templates/action.py
@@ -0,0 +1,145 @@
+:: # 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 itertools
+:: include('_copyright.py')
+
+:: include('_autogen.py')
+
+import struct
+import const
+import util
+import loxi
+
+def unpack_list(buf):
+ if len(buf) % 8 != 0: raise loxi.ProtocolError("action list length not a multiple of 8")
+ actions = []
+ offset = 0
+ while offset < len(buf):
+ type, length = struct.unpack_from("!HH", buf, offset)
+ if length == 0: raise loxi.ProtocolError("action length is 0")
+ if length % 8 != 0: raise loxi.ProtocolError("action length not a multiple of 8")
+ if offset + length > len(buf): raise loxi.ProtocolError("action length overruns list length")
+ parser = parsers.get(type)
+ if not parser: raise loxi.ProtocolError("unknown action type %d" % type)
+ actions.append(parser(buffer(buf, offset, length)))
+ offset += length
+ return actions
+
+class Action(object):
+ type = None # override in subclass
+ pass
+
+:: for ofclass in ofclasses:
+:: nonskip_members = [m for m in ofclass.members if not m.skip]
+class ${ofclass.pyname}(Action):
+:: for m in ofclass.type_members:
+ ${m.name} = ${m.value}
+:: #endfor
+
+ def __init__(self, ${', '.join(["%s=None" % m.name for m in nonskip_members])}):
+:: for m in nonskip_members:
+ if ${m.name} != None:
+ self.${m.name} = ${m.name}
+ else:
+ self.${m.name} = ${m.oftype.gen_init_expr()}
+:: #endfor
+
+ def pack(self):
+ packed = []
+:: include("_pack.py", ofclass=ofclass)
+ return ''.join(packed)
+
+ @staticmethod
+ def unpack(buf):
+ obj = ${ofclass.pyname}()
+:: include("_unpack.py", ofclass=ofclass)
+ return obj
+
+ def __eq__(self, other):
+ if type(self) != type(other): return False
+ if self.type != other.type: return False
+:: for m in nonskip_members:
+ if self.${m.name} != other.${m.name}: return False
+:: #endfor
+ return True
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def show(self):
+ import loxi.pp
+ return loxi.pp.pp(self)
+
+ def pretty_print(self, q):
+:: include('_pretty_print.py', ofclass=ofclass)
+
+:: #endfor
+
+def parse_vendor(buf):
+ if len(buf) < 16:
+ raise loxi.ProtocolError("experimenter action too short")
+
+ experimenter, = struct.unpack_from("!L", buf, 4)
+ if experimenter == 0x005c16c7: # Big Switch Networks
+ subtype, = struct.unpack_from("!L", buf, 8)
+ elif experimenter == 0x00002320: # Nicira
+ subtype, = struct.unpack_from("!H", buf, 8)
+ else:
+ raise loxi.ProtocolError("unexpected experimenter id %#x" % experimenter)
+
+ if subtype in experimenter_parsers[experimenter]:
+ return experimenter_parsers[experimenter][subtype](buf)
+ else:
+ raise loxi.ProtocolError("unexpected BSN experimenter subtype %#x" % subtype)
+
+parsers = {
+:: sort_key = lambda x: x.type_members[0].value
+:: msgtype_groups = itertools.groupby(sorted(ofclasses, key=sort_key), sort_key)
+:: for (k, v) in msgtype_groups:
+:: v = list(v)
+:: if len(v) == 1:
+ ${k} : ${v[0].pyname}.unpack,
+:: else:
+ ${k} : parse_${k[12:].lower()},
+:: #endif
+:: #endfor
+}
+
+:: experimenter_ofclasses = [x for x in ofclasses if x.type_members[0].value == 'const.OFPAT_VENDOR']
+:: sort_key = lambda x: x.type_members[1].value
+:: experimenter_ofclasses.sort(key=sort_key)
+:: grouped = itertools.groupby(experimenter_ofclasses, sort_key)
+experimenter_parsers = {
+:: for (experimenter, v) in grouped:
+ ${experimenter} : {
+:: for ofclass in v:
+ ${ofclass.type_members[2].value}: ${ofclass.pyname}.unpack,
+:: #endfor
+ },
+:: #endfor
+}
diff --git a/py_gen/templates/common.py b/py_gen/templates/common.py
new file mode 100644
index 0000000..08fb65d
--- /dev/null
+++ b/py_gen/templates/common.py
@@ -0,0 +1,125 @@
+:: # 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.
+::
+:: include('_copyright.py')
+
+:: include('_autogen.py')
+
+import sys
+import struct
+import action
+import const
+import util
+
+# HACK make this module visible as 'common' to simplify code generation
+common = sys.modules[__name__]
+
+def unpack_list_flow_stats_entry(buf):
+ entries = []
+ offset = 0
+ while offset < len(buf):
+ length, = struct.unpack_from("!H", buf, offset)
+ if length == 0: raise loxi.ProtocolError("entry length is 0")
+ if offset + length > len(buf): raise loxi.ProtocolError("entry length overruns list length")
+ entries.append(flow_stats_entry.unpack(buffer(buf, offset, length)))
+ offset += length
+ return entries
+
+def unpack_list_queue_prop(buf):
+ entries = []
+ offset = 0
+ while offset < len(buf):
+ type, length, = struct.unpack_from("!HH", buf, offset)
+ if length == 0: raise loxi.ProtocolError("entry length is 0")
+ if offset + length > len(buf): raise loxi.ProtocolError("entry length overruns list length")
+ if type == const.OFPQT_MIN_RATE:
+ entry = queue_prop_min_rate.unpack(buffer(buf, offset, length))
+ else:
+ raise loxi.ProtocolError("unknown queue prop %d" % type)
+ entries.append(entry)
+ offset += length
+ return entries
+
+def unpack_list_packet_queue(buf):
+ entries = []
+ offset = 0
+ while offset < len(buf):
+ _, length, = struct.unpack_from("!LH", buf, offset)
+ if length == 0: raise loxi.ProtocolError("entry length is 0")
+ if offset + length > len(buf): raise loxi.ProtocolError("entry length overruns list length")
+ entries.append(packet_queue.unpack(buffer(buf, offset, length)))
+ offset += length
+ return entries
+
+:: for ofclass in ofclasses:
+class ${ofclass.pyname}(object):
+:: for m in ofclass.type_members:
+ ${m.name} = ${m.value}
+:: #endfor
+
+ def __init__(self, ${', '.join(["%s=None" % m.name for m in ofclass.members])}):
+:: for m in ofclass.members:
+ if ${m.name} != None:
+ self.${m.name} = ${m.name}
+ else:
+ self.${m.name} = ${m.oftype.gen_init_expr()}
+:: #endfor
+
+ def pack(self):
+ packed = []
+:: include("_pack.py", ofclass=ofclass)
+ return ''.join(packed)
+
+ @staticmethod
+ def unpack(buf):
+ assert(len(buf) >= ${ofclass.min_length}) # Should be verified by caller
+ obj = ${ofclass.pyname}()
+:: include("_unpack.py", ofclass=ofclass)
+ return obj
+
+ def __eq__(self, other):
+ if type(self) != type(other): return False
+:: for m in ofclass.members:
+ if self.${m.name} != other.${m.name}: return False
+:: #endfor
+ return True
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def show(self):
+ import loxi.pp
+ return loxi.pp.pp(self)
+
+ def pretty_print(self, q):
+:: include('_pretty_print.py', ofclass=ofclass)
+
+:: if ofclass.name.startswith("of_match_v"):
+match = ${ofclass.pyname}
+
+:: #endif
+:: #endfor
diff --git a/py_gen/templates/const.py b/py_gen/templates/const.py
new file mode 100644
index 0000000..c5a0e93
--- /dev/null
+++ b/py_gen/templates/const.py
@@ -0,0 +1,66 @@
+:: # 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.
+::
+:: blacklisted_map_groups = ['macro_definitions']
+:: blacklisted_map_idents = ['OFPFW_NW_DST_BITS', 'OFPFW_NW_SRC_BITS',
+:: 'OFPFW_NW_SRC_SHIFT', 'OFPFW_NW_DST_SHIFT', 'OFPFW_NW_SRC_ALL',
+:: 'OFPFW_NW_SRC_MASK', 'OFPFW_NW_DST_ALL', 'OFPFW_NW_DST_MASK',
+:: 'OFPFW_ALL']
+:: include('_copyright.py')
+
+:: include('_autogen.py')
+
+OFP_VERSION = ${version}
+
+:: for (group, idents) in sorted(groups.items()):
+:: idents.sort(key=lambda (ident, value): eval(value))
+# Identifiers from group ${group}
+:: for (ident, value) in idents:
+:: if version == 1 and ident.startswith('OFPP_'):
+:: # HACK loxi converts these to 32-bit
+${ident} = ${"%#x" % (int(value, 16) & 0xffff)}
+:: else:
+${ident} = ${value}
+:: #endif
+:: #endfor
+
+:: if group not in blacklisted_map_groups:
+${group}_map = {
+:: for (ident, value) in idents:
+:: if ident in blacklisted_map_idents:
+:: pass
+:: elif version == 1 and ident.startswith('OFPP_'):
+:: # HACK loxi converts these to 32-bit
+ ${"%#x" % (int(value, 16) & 0xffff)}: ${repr(ident)},
+:: else:
+ ${value}: ${repr(ident)},
+:: #endif
+:: #endfor
+}
+
+:: #endif
+:: #endfor
diff --git a/py_gen/templates/init.py b/py_gen/templates/init.py
new file mode 100644
index 0000000..abc8d70
--- /dev/null
+++ b/py_gen/templates/init.py
@@ -0,0 +1,35 @@
+:: # 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.
+::
+:: include('_copyright.py')
+
+:: include('_autogen.py')
+
+import action, common, const, message
+from const import *
+from common import *
+from loxi import ProtocolError
diff --git a/py_gen/templates/message.py b/py_gen/templates/message.py
new file mode 100644
index 0000000..71a2871
--- /dev/null
+++ b/py_gen/templates/message.py
@@ -0,0 +1,219 @@
+:: # 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 itertools
+:: include('_copyright.py')
+
+:: include('_autogen.py')
+
+import struct
+import loxi
+import const
+import common
+import action # for unpack_list
+import util
+
+class Message(object):
+ version = const.OFP_VERSION
+ type = None # override in subclass
+ xid = None
+
+:: for ofclass in ofclasses:
+:: nonskip_members = [m for m in ofclass.members if not m.skip]
+class ${ofclass.pyname}(Message):
+:: for m in ofclass.type_members:
+ ${m.name} = ${m.value}
+:: #endfor
+
+ def __init__(self, ${', '.join(["%s=None" % m.name for m in nonskip_members])}):
+ self.xid = xid
+:: for m in [x for x in nonskip_members if x.name != 'xid']:
+ if ${m.name} != None:
+ self.${m.name} = ${m.name}
+ else:
+ self.${m.name} = ${m.oftype.gen_init_expr()}
+:: #endfor
+
+ def pack(self):
+ packed = []
+:: if ofclass.name == 'of_packet_out':
+:: include('_pack_packet_out.py', ofclass=ofclass)
+:: else:
+:: include('_pack.py', ofclass=ofclass)
+:: #endif
+ return ''.join(packed)
+
+ @staticmethod
+ def unpack(buf):
+ if len(buf) < 8: raise loxi.ProtocolError("buffer too short to contain an OpenFlow message")
+ obj = ${ofclass.pyname}()
+:: if ofclass.name == 'of_packet_out':
+:: include('_unpack_packet_out.py', ofclass=ofclass)
+:: else:
+:: include('_unpack.py', ofclass=ofclass)
+:: #endif
+ return obj
+
+ def __eq__(self, other):
+ if type(self) != type(other): return False
+ if self.version != other.version: return False
+ if self.type != other.type: return False
+:: for m in nonskip_members:
+ if self.${m.name} != other.${m.name}: return False
+:: #endfor
+ return True
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __str__(self):
+ return self.show()
+
+ def show(self):
+ import loxi.pp
+ return loxi.pp.pp(self)
+
+ def pretty_print(self, q):
+:: include('_pretty_print.py', ofclass=ofclass)
+
+:: #endfor
+
+def parse_header(buf):
+ if len(buf) < 8:
+ raise loxi.ProtocolError("too short to be an OpenFlow message")
+ return struct.unpack_from("!BBHL", buf)
+
+def parse_message(buf):
+ msg_ver, msg_type, msg_len, msg_xid = parse_header(buf)
+ if msg_ver != const.OFP_VERSION and msg_type != ofp.OFPT_HELLO:
+ raise loxi.ProtocolError("wrong OpenFlow version")
+ if len(buf) != msg_len:
+ raise loxi.ProtocolError("incorrect message size")
+ if msg_type in parsers:
+ return parsers[msg_type](buf)
+ else:
+ raise loxi.ProtocolError("unexpected message type")
+
+:: # TODO fix for OF 1.1+
+def parse_flow_mod(buf):
+ if len(buf) < 56 + 2:
+ raise loxi.ProtocolError("message too short")
+ cmd, = struct.unpack_from("!H", buf, 56)
+ if cmd in flow_mod_parsers:
+ return flow_mod_parsers[cmd](buf)
+ else:
+ raise loxi.ProtocolError("unexpected flow mod cmd %u" % cmd)
+
+def parse_stats_reply(buf):
+ if len(buf) < 8 + 2:
+ raise loxi.ProtocolError("message too short")
+ stats_type, = struct.unpack_from("!H", buf, 8)
+ if stats_type in stats_reply_parsers:
+ return stats_reply_parsers[stats_type](buf)
+ else:
+ raise loxi.ProtocolError("unexpected stats type %u" % stats_type)
+
+def parse_stats_request(buf):
+ if len(buf) < 8 + 2:
+ raise loxi.ProtocolError("message too short")
+ stats_type, = struct.unpack_from("!H", buf, 8)
+ if stats_type in stats_request_parsers:
+ return stats_request_parsers[stats_type](buf)
+ else:
+ raise loxi.ProtocolError("unexpected stats type %u" % stats_type)
+
+def parse_vendor(buf):
+ if len(buf) < 16:
+ raise loxi.ProtocolError("experimenter message too short")
+
+ experimenter, = struct.unpack_from("!L", buf, 8)
+ if experimenter == 0x005c16c7: # Big Switch Networks
+ subtype, = struct.unpack_from("!L", buf, 12)
+ elif experimenter == 0x00002320: # Nicira
+ subtype, = struct.unpack_from("!L", buf, 12)
+ else:
+ raise loxi.ProtocolError("unexpected experimenter id %#x" % experimenter)
+
+ if subtype in experimenter_parsers[experimenter]:
+ return experimenter_parsers[experimenter][subtype](buf)
+ else:
+ raise loxi.ProtocolError("unexpected experimenter %#x subtype %#x" % (experimenter, subtype))
+
+parsers = {
+:: sort_key = lambda x: x.type_members[1].value
+:: msgtype_groups = itertools.groupby(sorted(ofclasses, key=sort_key), sort_key)
+:: for (k, v) in msgtype_groups:
+:: v = list(v)
+:: if len(v) == 1:
+ ${k} : ${v[0].pyname}.unpack,
+:: else:
+ ${k} : parse_${k[11:].lower()},
+:: #endif
+:: #endfor
+}
+
+flow_mod_parsers = {
+ const.OFPFC_ADD : flow_add.unpack,
+ const.OFPFC_MODIFY : flow_modify.unpack,
+ const.OFPFC_MODIFY_STRICT : flow_modify_strict.unpack,
+ const.OFPFC_DELETE : flow_delete.unpack,
+ const.OFPFC_DELETE_STRICT : flow_delete_strict.unpack,
+}
+
+stats_reply_parsers = {
+ const.OFPST_DESC : desc_stats_reply.unpack,
+ const.OFPST_FLOW : flow_stats_reply.unpack,
+ const.OFPST_AGGREGATE : aggregate_stats_reply.unpack,
+ const.OFPST_TABLE : table_stats_reply.unpack,
+ const.OFPST_PORT : port_stats_reply.unpack,
+ const.OFPST_QUEUE : queue_stats_reply.unpack,
+ const.OFPST_VENDOR : experimenter_stats_reply.unpack,
+}
+
+stats_request_parsers = {
+ const.OFPST_DESC : desc_stats_request.unpack,
+ const.OFPST_FLOW : flow_stats_request.unpack,
+ const.OFPST_AGGREGATE : aggregate_stats_request.unpack,
+ const.OFPST_TABLE : table_stats_request.unpack,
+ const.OFPST_PORT : port_stats_request.unpack,
+ const.OFPST_QUEUE : queue_stats_request.unpack,
+ const.OFPST_VENDOR : experimenter_stats_request.unpack,
+}
+
+:: experimenter_ofclasses = [x for x in ofclasses if x.type_members[1].value == 'const.OFPT_VENDOR']
+:: sort_key = lambda x: x.type_members[2].value
+:: experimenter_ofclasses.sort(key=sort_key)
+:: grouped = itertools.groupby(experimenter_ofclasses, sort_key)
+experimenter_parsers = {
+:: for (experimenter, v) in grouped:
+ ${experimenter} : {
+:: for ofclass in v:
+ ${ofclass.type_members[3].value}: ${ofclass.pyname}.unpack,
+:: #endfor
+ },
+:: #endfor
+}
diff --git a/py_gen/templates/pp.py b/py_gen/templates/pp.py
new file mode 100644
index 0000000..0021a28
--- /dev/null
+++ b/py_gen/templates/pp.py
@@ -0,0 +1,277 @@
+:: # 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.
+::
+# Copyright 2013, Big Switch Networks, Inc.
+
+"""
+pp - port of Ruby's PP library
+Also based on Lindig, C., & GbR, G. D. (2000). Strictly Pretty.
+
+Example usage:
+>>> import pp.pp as pp
+>>> print pp([[1, 2], [3, 4]], maxwidth=15)
+[
+ [ 1, 2 ],
+ [ 3, 4 ]
+]
+"""
+import unittest
+from contextlib import contextmanager
+
+def pp(obj, maxwidth=79):
+ """
+ Pretty-print the given object.
+ """
+ ctx = PrettyPrinter(maxwidth=maxwidth)
+ ctx.pp(obj)
+ return str(ctx)
+
+
+## Pretty-printers for builtin classes
+
+def pretty_print_list(pp, obj):
+ with pp.group():
+ pp.text('[')
+ with pp.indent(2):
+ for v in obj:
+ if not pp.first(): pp.text(',')
+ pp.breakable()
+ pp.pp(v)
+ pp.breakable()
+ pp.text(']')
+
+def pretty_print_dict(pp, obj):
+ with pp.group():
+ pp.text('{')
+ with pp.indent(2):
+ for (k, v) in sorted(obj.items()):
+ if not pp.first(): pp.text(',')
+ pp.breakable()
+ pp.pp(k)
+ pp.text(': ')
+ pp.pp(v)
+ pp.breakable()
+ pp.text('}')
+
+pretty_printers = {
+ list: pretty_print_list,
+ dict: pretty_print_dict,
+}
+
+
+## Implementation
+
+class PrettyPrinter(object):
+ def __init__(self, maxwidth):
+ self.maxwidth = maxwidth
+ self.cur_indent = 0
+ self.root_group = Group()
+ self.group_stack = [self.root_group]
+
+ def current_group(self):
+ return self.group_stack[-1]
+
+ def text(self, s):
+ self.current_group().append(str(s))
+
+ def breakable(self, sep=' '):
+ self.current_group().append(Breakable(sep, self.cur_indent))
+
+ def first(self):
+ return self.current_group().first()
+
+ @contextmanager
+ def indent(self, n):
+ self.cur_indent += n
+ yield
+ self.cur_indent -= n
+
+ @contextmanager
+ def group(self):
+ self.group_stack.append(Group())
+ yield
+ new_group = self.group_stack.pop()
+ self.current_group().append(new_group)
+
+ def pp(self, obj):
+ if hasattr(obj, "pretty_print"):
+ obj.pretty_print(self)
+ elif type(obj) in pretty_printers:
+ pretty_printers[type(obj)](self, obj)
+ else:
+ self.text(repr(obj))
+
+ def __str__(self):
+ return self.root_group.render(0, self.maxwidth)
+
+class Group(object):
+ __slots__ = ["fragments", "length", "_first"]
+
+ def __init__(self):
+ self.fragments = []
+ self.length = 0
+ self._first = True
+
+ def append(self, x):
+ self.fragments.append(x)
+ self.length += len(x)
+
+ def first(self):
+ if self._first:
+ self._first = False
+ return True
+ return False
+
+ def __len__(self):
+ return self.length
+
+ def render(self, curwidth, maxwidth):
+ dobreak = len(self) > (maxwidth - curwidth)
+
+ a = []
+ for x in self.fragments:
+ if isinstance(x, Breakable):
+ if dobreak:
+ a.append('\n')
+ a.append(' ' * x.indent)
+ curwidth = 0
+ else:
+ a.append(x.sep)
+ elif isinstance(x, Group):
+ a.append(x.render(curwidth, maxwidth))
+ else:
+ a.append(x)
+ curwidth += len(a[-1])
+ return ''.join(a)
+
+class Breakable(object):
+ __slots__ = ["sep", "indent"]
+
+ def __init__(self, sep, indent):
+ self.sep = sep
+ self.indent = indent
+
+ def __len__(self):
+ return len(self.sep)
+
+
+## Tests
+
+class TestPP(unittest.TestCase):
+ def test_scalars(self):
+ self.assertEquals(pp(1), "1")
+ self.assertEquals(pp("foo"), "'foo'")
+
+ def test_hash(self):
+ expected = """{ 1: 'a', 'b': 2 }"""
+ self.assertEquals(pp(eval(expected)), expected)
+ expected = """\
+{
+ 1: 'a',
+ 'b': 2
+}"""
+ self.assertEquals(pp(eval(expected), maxwidth=0), expected)
+
+ def test_array(self):
+ expected = """[ 1, 'a', 2 ]"""
+ self.assertEquals(pp(eval(expected)), expected)
+ expected = """\
+[
+ 1,
+ 'a',
+ 2
+]"""
+ self.assertEquals(pp(eval(expected), maxwidth=0), expected)
+
+ def test_nested(self):
+ expected = """[ [ 1, 2 ], [ 3, 4 ] ]"""
+ self.assertEquals(pp(eval(expected)), expected)
+ expected = """\
+[
+ [
+ 1,
+ 2
+ ],
+ [
+ 3,
+ 4
+ ]
+]"""
+ self.assertEquals(pp(eval(expected), maxwidth=0), expected)
+
+ def test_breaking(self):
+ expected = """\
+[
+ [ 1, 2 ],
+ 'abcdefghijklmnopqrstuvwxyz'
+]"""
+ self.assertEquals(pp(eval(expected), maxwidth=24), expected)
+ expected = """\
+[
+ [ 'abcd', 2 ],
+ [ '0123456789' ],
+ [
+ '0123456789',
+ 'abcdefghij'
+ ],
+ [ 'abcdefghijklmnop' ],
+ [
+ 'abcdefghijklmnopq'
+ ],
+ { 'k': 'v' },
+ {
+ 1: [ 2, [ 3, 4 ] ],
+ 'foo': 'abcdefghijklmnop'
+ }
+]"""
+ self.assertEquals(pp(eval(expected), maxwidth=24), expected)
+ expected = """\
+[
+ [ 1, 2 ],
+ [ 3, 4 ]
+]"""
+ self.assertEquals(pp(eval(expected), maxwidth=15), expected)
+
+ # This is an edge case where our simpler algorithm breaks down.
+ @unittest.expectedFailure
+ def test_greedy_breaking(self):
+ expected = """\
+abc def
+ghijklmnopqrstuvwxyz\
+"""
+ pp = PrettyPrinter(maxwidth=8)
+ pp.text("abc")
+ with pp.group():
+ pp.breakable()
+ pp.text("def")
+ with pp.group():
+ pp.breakable()
+ pp.text("ghijklmnopqrstuvwxyz")
+ self.assertEquals(str(pp), expected)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/py_gen/templates/toplevel_init.py b/py_gen/templates/toplevel_init.py
new file mode 100644
index 0000000..c990aa3
--- /dev/null
+++ b/py_gen/templates/toplevel_init.py
@@ -0,0 +1,46 @@
+:: # 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.
+::
+:: include('_copyright.py')
+
+:: include('_autogen.py')
+
+def protocol(ver):
+ """
+ Import and return the protocol module for the given wire version.
+ """
+ if ver == 1:
+ import of10
+ return of10
+ else:
+ raise ValueError
+
+class ProtocolError(Exception):
+ """
+ Raised when failing to deserialize an invalid OpenFlow message.
+ """
+ pass
diff --git a/py_gen/templates/util.py b/py_gen/templates/util.py
new file mode 100644
index 0000000..5933e14
--- /dev/null
+++ b/py_gen/templates/util.py
@@ -0,0 +1,77 @@
+:: # 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.
+::
+:: include('_copyright.py')
+
+:: include('_autogen.py')
+
+import loxi
+import const
+
+def unpack_array(deserializer, element_size, buf):
+ """
+ Deserialize an array of fixed length elements.
+ The deserializer function should take a buffer and return the new object.
+ """
+ if len(buf) % element_size != 0: raise loxi.ProtocolError("invalid array length")
+ n = len(buf) / element_size
+ return [deserializer(buffer(buf, i*element_size, element_size)) for i in range(n)]
+
+def pretty_mac(mac):
+ return ':'.join(["%02x" % x for x in mac])
+
+def pretty_ipv4(v):
+ return "%d.%d.%d.%d" % ((v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF)
+
+def pretty_flags(v, flag_names):
+ set_flags = []
+ for flag_name in flag_names:
+ flag_value = getattr(const, flag_name)
+ if v & flag_value == flag_value:
+ set_flags.append(flag_name)
+ elif v & flag_value:
+ set_flags.append('%s&%#x' % (flag_name, v & flag_value))
+ v &= ~flag_value
+ if v:
+ set_flags.append("%#x" % v)
+ return '|'.join(set_flags) or '0'
+
+def pretty_wildcards(v):
+ if v == const.OFPFW_ALL:
+ return 'OFPFW_ALL'
+ flag_names = ['OFPFW_IN_PORT', 'OFPFW_DL_VLAN', 'OFPFW_DL_SRC', 'OFPFW_DL_DST',
+ 'OFPFW_DL_TYPE', 'OFPFW_NW_PROTO', 'OFPFW_TP_SRC', 'OFPFW_TP_DST',
+ 'OFPFW_NW_SRC_MASK', 'OFPFW_NW_DST_MASK', 'OFPFW_DL_VLAN_PCP',
+ 'OFPFW_NW_TOS']
+ return pretty_flags(v, flag_names)
+
+def pretty_port(v):
+ named_ports = [(k,v2) for (k,v2) in const.__dict__.iteritems() if k.startswith('OFPP_')]
+ for (k, v2) in named_ports:
+ if v == v2:
+ return k
+ return v
diff --git a/py_gen/tests.py b/py_gen/tests.py
new file mode 100644
index 0000000..23d40b8
--- /dev/null
+++ b/py_gen/tests.py
@@ -0,0 +1,1002 @@
+#!/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 unittest
+
+try:
+ import loxi
+ del loxi
+except ImportError:
+ exit("loxi package not found. Try setting PYTHONPATH.")
+
+class TestImports(unittest.TestCase):
+ def test_toplevel(self):
+ import loxi
+ self.assertTrue(hasattr(loxi, "ProtocolError"))
+ ofp = loxi.protocol(1)
+ self.assertEquals(ofp.OFP_VERSION, 1)
+ self.assertTrue(hasattr(ofp, "action"))
+ self.assertTrue(hasattr(ofp, "common"))
+ self.assertTrue(hasattr(ofp, "const"))
+ self.assertTrue(hasattr(ofp, "message"))
+
+ def test_version(self):
+ import loxi.of10
+ self.assertTrue(hasattr(loxi.of10, "ProtocolError"))
+ self.assertTrue(hasattr(loxi.of10, "OFP_VERSION"))
+ self.assertEquals(loxi.of10.OFP_VERSION, 1)
+ self.assertTrue(hasattr(loxi.of10, "action"))
+ self.assertTrue(hasattr(loxi.of10, "common"))
+ self.assertTrue(hasattr(loxi.of10, "const"))
+ self.assertTrue(hasattr(loxi.of10, "message"))
+
+class TestActions(unittest.TestCase):
+ def test_output_pack(self):
+ import loxi.of10 as ofp
+ expected = "\x00\x00\x00\x08\xff\xf8\xff\xff"
+ action = ofp.action.output(port=ofp.OFPP_IN_PORT, max_len=0xffff)
+ self.assertEquals(expected, action.pack())
+
+ def test_output_unpack(self):
+ import loxi.of10 as ofp
+
+ # Normal case
+ buf = "\x00\x00\x00\x08\xff\xf8\xff\xff"
+ action = ofp.action.output.unpack(buf)
+ self.assertEqual(action.port, ofp.OFPP_IN_PORT)
+ self.assertEqual(action.max_len, 0xffff)
+
+ # Invalid length
+ buf = "\x00\x00\x00\x09\xff\xf8\xff\xff\x00"
+ with self.assertRaises(ofp.ProtocolError):
+ ofp.action.output.unpack(buf)
+
+ def test_output_equality(self):
+ import loxi.of10 as ofp
+ action = ofp.action.output(port=1, max_len=0x1234)
+ action2 = ofp.action.output(port=1, max_len=0x1234)
+ self.assertEquals(action, action2)
+
+ action2.port = 2
+ self.assertNotEquals(action, action2)
+ action2.port = 1
+
+ action2.max_len = 0xffff
+ self.assertNotEquals(action, action2)
+ action2.max_len = 0x1234
+
+ def test_output_show(self):
+ import loxi.of10 as ofp
+ action = ofp.action.output(port=1, max_len=0x1234)
+ expected = "output { port = 1, max_len = 0x1234 }"
+ self.assertEquals(expected, action.show())
+
+ def test_bsn_set_tunnel_dst_pack(self):
+ import loxi.of10 as ofp
+ expected = ''.join([
+ "\xff\xff", "\x00\x10", # type/length
+ "\x00\x5c\x16\xc7", # experimenter
+ "\x00\x00\x00\x02", # subtype
+ "\x12\x34\x56\x78" # dst
+ ])
+ action = ofp.action.bsn_set_tunnel_dst(dst=0x12345678)
+ self.assertEquals(expected, action.pack())
+
+ def test_bsn_set_tunnel_dst_unpack(self):
+ import loxi.of10 as ofp
+ buf = ''.join([
+ "\xff\xff", "\x00\x10", # type/length
+ "\x00\x5c\x16\xc7", # experimenter
+ "\x00\x00\x00\x02", # subtype
+ "\x12\x34\x56\x78" # dst
+ ])
+ action = ofp.action.bsn_set_tunnel_dst.unpack(buf)
+ self.assertEqual(action.subtype, 2)
+ self.assertEqual(action.dst, 0x12345678)
+
+# Assumes action serialization/deserialization works
+class TestActionList(unittest.TestCase):
+ def test_normal(self):
+ import loxi.of10 as ofp
+
+ expected = []
+ bufs = []
+
+ def add(action):
+ expected.append(action)
+ bufs.append(action.pack())
+
+ add(ofp.action.output(port=1, max_len=0xffff))
+ add(ofp.action.output(port=2, max_len=0xffff))
+ add(ofp.action.output(port=ofp.OFPP_IN_PORT, max_len=0xffff))
+ add(ofp.action.bsn_set_tunnel_dst(dst=0x12345678))
+ add(ofp.action.nicira_dec_ttl())
+
+ actions = ofp.action.unpack_list(''.join(bufs))
+ self.assertEquals(actions, expected)
+
+ def test_empty_list(self):
+ import loxi.of10 as ofp
+ self.assertEquals(ofp.action.unpack_list(''), [])
+
+ def test_invalid_list_length(self):
+ import loxi.of10 as ofp
+ buf = '\x00' * 9
+ with self.assertRaisesRegexp(ofp.ProtocolError, 'not a multiple of 8'):
+ ofp.action.unpack_list(buf)
+
+ def test_invalid_action_length(self):
+ import loxi.of10 as ofp
+
+ buf = '\x00' * 8
+ with self.assertRaisesRegexp(ofp.ProtocolError, 'is 0'):
+ ofp.action.unpack_list(buf)
+
+ buf = '\x00\x00\x00\x04'
+ with self.assertRaisesRegexp(ofp.ProtocolError, 'not a multiple of 8'):
+ ofp.action.unpack_list(buf)
+
+ buf = '\x00\x00\x00\x10\x00\x00\x00\x00'
+ with self.assertRaisesRegexp(ofp.ProtocolError, 'overrun'):
+ ofp.action.unpack_list(buf)
+
+ def test_invalid_action_type(self):
+ import loxi.of10 as ofp
+ buf = '\xff\xfe\x00\x08\x00\x00\x00\x00'
+ with self.assertRaisesRegexp(ofp.ProtocolError, 'unknown action type'):
+ ofp.action.unpack_list(buf)
+
+class TestConstants(unittest.TestCase):
+ def test_ports(self):
+ import loxi.of10 as ofp
+ self.assertEquals(0xffff, ofp.OFPP_NONE)
+
+ def test_wildcards(self):
+ import loxi.of10 as ofp
+ self.assertEquals(0xfc000, ofp.OFPFW_NW_DST_MASK)
+
+class TestCommon(unittest.TestCase):
+ def test_port_desc_pack(self):
+ import loxi.of10 as ofp
+ obj = ofp.port_desc(port_no=ofp.OFPP_CONTROLLER,
+ hw_addr=[1,2,3,4,5,6],
+ name="foo",
+ config=ofp.OFPPC_NO_FLOOD,
+ state=ofp.OFPPS_STP_FORWARD,
+ curr=ofp.OFPPF_10MB_HD,
+ advertised=ofp.OFPPF_1GB_FD,
+ supported=ofp.OFPPF_AUTONEG,
+ peer=ofp.OFPPF_PAUSE_ASYM)
+ expected = ''.join([
+ '\xff\xfd', # port_no
+ '\x01\x02\x03\x04\x05\x06', # hw_addr
+ 'foo'.ljust(16, '\x00'), # name
+ '\x00\x00\x00\x10', # config
+ '\x00\x00\x02\x00', # state
+ '\x00\x00\x00\x01', # curr
+ '\x00\x00\x00\x20', # advertised
+ '\x00\x00\x02\x00', # supported
+ '\x00\x00\x08\x00', # peer
+ ])
+ self.assertEquals(expected, obj.pack())
+
+ def test_port_desc_unpack(self):
+ import loxi.of10 as ofp
+ buf = ''.join([
+ '\xff\xfd', # port_no
+ '\x01\x02\x03\x04\x05\x06', # hw_addr
+ 'foo'.ljust(16, '\x00'), # name
+ '\x00\x00\x00\x10', # config
+ '\x00\x00\x02\x00', # state
+ '\x00\x00\x00\x01', # curr
+ '\x00\x00\x00\x20', # advertised
+ '\x00\x00\x02\x00', # supported
+ '\x00\x00\x08\x00', # peer
+ ])
+ obj = ofp.port_desc.unpack(buf)
+ self.assertEquals(ofp.OFPP_CONTROLLER, obj.port_no)
+ self.assertEquals('foo', obj.name)
+ self.assertEquals(ofp.OFPPF_PAUSE_ASYM, obj.peer)
+
+ def test_table_stats_entry_pack(self):
+ import loxi.of10 as ofp
+ obj = ofp.table_stats_entry(table_id=3,
+ name="foo",
+ wildcards=ofp.OFPFW_ALL,
+ max_entries=5,
+ active_count=2,
+ lookup_count=1099511627775,
+ matched_count=9300233470495232273L)
+ expected = ''.join([
+ '\x03', # table_id
+ '\x00\x00\x00', # pad
+ 'foo'.ljust(32, '\x00'), # name
+ '\x00\x3f\xFF\xFF', # wildcards
+ '\x00\x00\x00\x05', # max_entries
+ '\x00\x00\x00\x02', # active_count
+ '\x00\x00\x00\xff\xff\xff\xff\xff', # lookup_count
+ '\x81\x11\x11\x11\x11\x11\x11\x11', # matched_count
+ ])
+ self.assertEquals(expected, obj.pack())
+
+ def test_table_stats_entry_unpack(self):
+ import loxi.of10 as ofp
+ buf = ''.join([
+ '\x03', # table_id
+ '\x00\x00\x00', # pad
+ 'foo'.ljust(32, '\x00'), # name
+ '\x00\x3f\xFF\xFF', # wildcards
+ '\x00\x00\x00\x05', # max_entries
+ '\x00\x00\x00\x02', # active_count
+ '\x00\x00\x00\xff\xff\xff\xff\xff', # lookup_count
+ '\x81\x11\x11\x11\x11\x11\x11\x11', # matched_count
+ ])
+ obj = ofp.table_stats_entry.unpack(buf)
+ self.assertEquals(3, obj.table_id)
+ self.assertEquals('foo', obj.name)
+ self.assertEquals(9300233470495232273L, obj.matched_count)
+
+ def test_flow_stats_entry_pack(self):
+ import loxi.of10 as ofp
+ obj = ofp.flow_stats_entry(table_id=3,
+ match=ofp.match(),
+ duration_sec=1,
+ duration_nsec=2,
+ priority=100,
+ idle_timeout=5,
+ hard_timeout=10,
+ cookie=0x0123456789abcdef,
+ packet_count=10,
+ byte_count=1000,
+ actions=[ofp.action.output(port=1),
+ ofp.action.output(port=2)])
+ expected = ''.join([
+ '\x00\x68', # length
+ '\x03', # table_id
+ '\x00', # pad
+ '\x00\x3f\xff\xff', # match.wildcards
+ '\x00' * 36, # remaining match fields
+ '\x00\x00\x00\x01', # duration_sec
+ '\x00\x00\x00\x02', # duration_nsec
+ '\x00\x64', # priority
+ '\x00\x05', # idle_timeout
+ '\x00\x0a', # hard_timeout
+ '\x00' * 6, # pad2
+ '\x01\x23\x45\x67\x89\xab\xcd\xef', # cookie
+ '\x00\x00\x00\x00\x00\x00\x00\x0a', # packet_count
+ '\x00\x00\x00\x00\x00\x00\x03\xe8', # byte_count
+ '\x00\x00', # actions[0].type
+ '\x00\x08', # actions[0].len
+ '\x00\x01', # actions[0].port
+ '\x00\x00', # actions[0].max_len
+ '\x00\x00', # actions[1].type
+ '\x00\x08', # actions[1].len
+ '\x00\x02', # actions[1].port
+ '\x00\x00', # actions[1].max_len
+ ])
+ self.assertEquals(expected, obj.pack())
+
+ def test_flow_stats_entry_unpack(self):
+ import loxi.of10 as ofp
+ buf = ''.join([
+ '\x00\x68', # length
+ '\x03', # table_id
+ '\x00', # pad
+ '\x00\x3f\xff\xff', # match.wildcards
+ '\x00' * 36, # remaining match fields
+ '\x00\x00\x00\x01', # duration_sec
+ '\x00\x00\x00\x02', # duration_nsec
+ '\x00\x64', # priority
+ '\x00\x05', # idle_timeout
+ '\x00\x0a', # hard_timeout
+ '\x00' * 6, # pad2
+ '\x01\x23\x45\x67\x89\xab\xcd\xef', # cookie
+ '\x00\x00\x00\x00\x00\x00\x00\x0a', # packet_count
+ '\x00\x00\x00\x00\x00\x00\x03\xe8', # byte_count
+ '\x00\x00', # actions[0].type
+ '\x00\x08', # actions[0].len
+ '\x00\x01', # actions[0].port
+ '\x00\x00', # actions[0].max_len
+ '\x00\x00', # actions[1].type
+ '\x00\x08', # actions[1].len
+ '\x00\x02', # actions[1].port
+ '\x00\x00', # actions[1].max_len
+ ])
+ obj = ofp.flow_stats_entry.unpack(buf)
+ self.assertEquals(3, obj.table_id)
+ self.assertEquals(ofp.OFPFW_ALL, obj.match.wildcards)
+ self.assertEquals(2, len(obj.actions))
+ self.assertEquals(1, obj.actions[0].port)
+ self.assertEquals(2, obj.actions[1].port)
+
+ def test_match(self):
+ import loxi.of10 as ofp
+ match = ofp.match()
+ self.assertEquals(match.wildcards, ofp.OFPFW_ALL)
+ self.assertEquals(match.tcp_src, 0)
+ buf = match.pack()
+ match2 = ofp.match.unpack(buf)
+ self.assertEquals(match, match2)
+
+class TestMessages(unittest.TestCase):
+ def test_hello_construction(self):
+ import loxi.of10 as ofp
+
+ msg = ofp.message.hello()
+ self.assertEquals(msg.version, ofp.OFP_VERSION)
+ self.assertEquals(msg.type, ofp.OFPT_HELLO)
+ self.assertEquals(msg.xid, None)
+
+ msg = ofp.message.hello(xid=123)
+ self.assertEquals(msg.xid, 123)
+
+ # 0 is a valid xid distinct from None
+ msg = ofp.message.hello(xid=0)
+ self.assertEquals(msg.xid, 0)
+
+ def test_hello_unpack(self):
+ import loxi.of10 as ofp
+
+ # Normal case
+ buf = "\x01\x00\x00\x08\x12\x34\x56\x78"
+ msg = ofp.message.hello(xid=0x12345678)
+ self.assertEquals(buf, msg.pack())
+
+ # Invalid length
+ buf = "\x01\x00\x00\x09\x12\x34\x56\x78\x9a"
+ with self.assertRaisesRegexp(ofp.ProtocolError, "should be 8"):
+ ofp.message.hello.unpack(buf)
+
+ def test_echo_request_construction(self):
+ import loxi.of10 as ofp
+ msg = ofp.message.echo_request(data="abc")
+ self.assertEquals(msg.data, "abc")
+
+ def test_echo_request_pack(self):
+ import loxi.of10 as ofp
+
+ msg = ofp.message.echo_request(xid=0x12345678, data="abc")
+ buf = msg.pack()
+ self.assertEquals(buf, "\x01\x02\x00\x0b\x12\x34\x56\x78\x61\x62\x63")
+
+ msg2 = ofp.message.echo_request.unpack(buf)
+ self.assertEquals(msg, msg2)
+
+ def test_echo_request_unpack(self):
+ import loxi.of10 as ofp
+
+ # Normal case
+ buf = "\x01\x02\x00\x0b\x12\x34\x56\x78\x61\x62\x63"
+ msg = ofp.message.echo_request(xid=0x12345678, data="abc")
+ self.assertEquals(buf, msg.pack())
+
+ # Invalid length
+ buf = "\x01\x02\x00\x07\x12\x34\x56"
+ with self.assertRaisesRegexp(ofp.ProtocolError, "buffer too short"):
+ ofp.message.echo_request.unpack(buf)
+
+ def test_echo_request_equality(self):
+ import loxi.of10 as ofp
+
+ msg = ofp.message.echo_request(xid=0x12345678, data="abc")
+ msg2 = ofp.message.echo_request(xid=0x12345678, data="abc")
+ #msg2 = ofp.message.echo_request.unpack(msg.pack())
+ self.assertEquals(msg, msg2)
+
+ msg2.xid = 1
+ self.assertNotEquals(msg, msg2)
+ msg2.xid = msg.xid
+
+ msg2.data = "a"
+ self.assertNotEquals(msg, msg2)
+ msg2.data = msg.data
+
+ def test_echo_request_show(self):
+ import loxi.of10 as ofp
+ expected = "echo_request { xid = 0x12345678, data = 'ab\\x01' }"
+ msg = ofp.message.echo_request(xid=0x12345678, data="ab\x01")
+ self.assertEquals(msg.show(), expected)
+
+ def test_flow_add(self):
+ import loxi.of10 as ofp
+ match = ofp.match()
+ msg = ofp.message.flow_add(xid=1,
+ match=match,
+ cookie=1,
+ idle_timeout=5,
+ flags=ofp.OFPFF_CHECK_OVERLAP,
+ actions=[
+ ofp.action.output(port=1),
+ ofp.action.output(port=2),
+ ofp.action.output(port=ofp.OFPP_CONTROLLER,
+ max_len=1024)])
+ buf = msg.pack()
+ msg2 = ofp.message.flow_add.unpack(buf)
+ self.assertEquals(msg, msg2)
+
+ def test_port_mod_pack(self):
+ import loxi.of10 as ofp
+ msg = ofp.message.port_mod(xid=2,
+ port_no=ofp.OFPP_CONTROLLER,
+ hw_addr=[1,2,3,4,5,6],
+ config=0x90ABCDEF,
+ mask=0xFF11FF11,
+ advertise=0xCAFE6789)
+ expected = "\x01\x0f\x00\x20\x00\x00\x00\x02\xff\xfd\x01\x02\x03\x04\x05\x06\x90\xab\xcd\xef\xff\x11\xff\x11\xca\xfe\x67\x89\x00\x00\x00\x00"
+ self.assertEquals(expected, msg.pack())
+
+ def test_desc_stats_reply_pack(self):
+ import loxi.of10 as ofp
+ msg = ofp.message.desc_stats_reply(xid=3,
+ flags=ofp.OFPSF_REPLY_MORE,
+ mfr_desc="The Indigo-2 Community",
+ hw_desc="Unknown server",
+ sw_desc="Indigo-2 LRI pre-release",
+ serial_num="11235813213455",
+ dp_desc="Indigo-2 LRI forwarding module")
+ expected = ''.join([
+ '\x01', '\x11', # version/type
+ '\x04\x2c', # length
+ '\x00\x00\x00\x03', # xid
+ '\x00\x00', # stats_type
+ '\x00\x01', # flags
+ 'The Indigo-2 Community'.ljust(256, '\x00'), # mfr_desc
+ 'Unknown server'.ljust(256, '\x00'), # hw_desc
+ 'Indigo-2 LRI pre-release'.ljust(256, '\x00'), # sw_desc
+ '11235813213455'.ljust(32, '\x00'), # serial_num
+ 'Indigo-2 LRI forwarding module'.ljust(256, '\x00'), # dp_desc
+ ])
+ self.assertEquals(expected, msg.pack())
+
+ def test_desc_stats_reply_unpack(self):
+ import loxi.of10 as ofp
+ buf = ''.join([
+ '\x01', '\x11', # version/type
+ '\x04\x2c', # length
+ '\x00\x00\x00\x03', # xid
+ '\x00\x00', # stats_type
+ '\x00\x01', # flags
+ 'The Indigo-2 Community'.ljust(256, '\x00'), # mfr_desc
+ 'Unknown server'.ljust(256, '\x00'), # hw_desc
+ 'Indigo-2 LRI pre-release'.ljust(256, '\x00'), # sw_desc
+ '11235813213455'.ljust(32, '\x00'), # serial_num
+ 'Indigo-2 LRI forwarding module'.ljust(256, '\x00'), # dp_desc
+ ])
+ msg = ofp.message.desc_stats_reply.unpack(buf)
+ self.assertEquals('Indigo-2 LRI forwarding module', msg.dp_desc)
+ self.assertEquals('11235813213455', msg.serial_num)
+ self.assertEquals(ofp.OFPST_DESC, msg.stats_type)
+ self.assertEquals(ofp.OFPSF_REPLY_MORE, msg.flags)
+
+ def test_port_status_pack(self):
+ import loxi.of10 as ofp
+
+ desc = ofp.port_desc(port_no=ofp.OFPP_CONTROLLER,
+ hw_addr=[1,2,3,4,5,6],
+ name="foo",
+ config=ofp.OFPPC_NO_FLOOD,
+ state=ofp.OFPPS_STP_FORWARD,
+ curr=ofp.OFPPF_10MB_HD,
+ advertised=ofp.OFPPF_1GB_FD,
+ supported=ofp.OFPPF_AUTONEG,
+ peer=ofp.OFPPF_PAUSE_ASYM)
+
+ msg = ofp.message.port_status(xid=4,
+ reason=ofp.OFPPR_DELETE,
+ desc=desc)
+ expected = ''.join([
+ '\x01', '\x0c', # version/type
+ '\x00\x40', # length
+ '\x00\x00\x00\x04', # xid
+ '\x01', # reason
+ '\x00\x00\x00\x00\x00\x00\x00' # pad
+ '\xff\xfd', # desc.port_no
+ '\x01\x02\x03\x04\x05\x06', # desc.hw_addr
+ 'foo'.ljust(16, '\x00'), # desc.name
+ '\x00\x00\x00\x10', # desc.config
+ '\x00\x00\x02\x00', # desc.state
+ '\x00\x00\x00\x01', # desc.curr
+ '\x00\x00\x00\x20', # desc.advertised
+ '\x00\x00\x02\x00', # desc.supported
+ '\x00\x00\x08\x00', # desc.peer
+ ])
+ self.assertEquals(expected, msg.pack())
+
+ def test_port_status_unpack(self):
+ import loxi.of10 as ofp
+ buf = ''.join([
+ '\x01', '\x0c', # version/type
+ '\x00\x40', # length
+ '\x00\x00\x00\x04', # xid
+ '\x01', # reason
+ '\x00\x00\x00\x00\x00\x00\x00' # pad
+ '\xff\xfd', # desc.port_no
+ '\x01\x02\x03\x04\x05\x06', # desc.hw_addr
+ 'foo'.ljust(16, '\x00'), # desc.name
+ '\x00\x00\x00\x10', # desc.config
+ '\x00\x00\x02\x00', # desc.state
+ '\x00\x00\x00\x01', # desc.curr
+ '\x00\x00\x00\x20', # desc.advertised
+ '\x00\x00\x02\x00', # desc.supported
+ '\x00\x00\x08\x00', # desc.peer
+ ])
+ msg = ofp.message.port_status.unpack(buf)
+ self.assertEquals('foo', msg.desc.name)
+ self.assertEquals(ofp.OFPPF_PAUSE_ASYM, msg.desc.peer)
+
+ def test_port_stats_reply_pack(self):
+ import loxi.of10 as ofp
+ msg = ofp.message.port_stats_reply(xid=5, flags=0, entries=[
+ ofp.port_stats_entry(port_no=1, rx_packets=56, collisions=5),
+ ofp.port_stats_entry(port_no=ofp.OFPP_LOCAL, rx_packets=1, collisions=1)])
+ expected = ''.join([
+ '\x01', '\x11', # version/type
+ '\x00\xdc', # length
+ '\x00\x00\x00\x05', # xid
+ '\x00\x04', # stats_type
+ '\x00\x00', # flags
+ '\x00\x01', # entries[0].port_no
+ '\x00\x00\x00\x00\x00\x00' # entries[0].pad
+ '\x00\x00\x00\x00\x00\x00\x00\x38', # entries[0].rx_packets
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].tx_packets
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_bytes
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].tx_bytes
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_dropped
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].tx_dropped
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_errors
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].tx_errors
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_frame_err
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_over_err
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_crc_err
+ '\x00\x00\x00\x00\x00\x00\x00\x05', # entries[0].collisions
+ '\xff\xfe', # entries[1].port_no
+ '\x00\x00\x00\x00\x00\x00' # entries[1].pad
+ '\x00\x00\x00\x00\x00\x00\x00\x01', # entries[1].rx_packets
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].tx_packets
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_bytes
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].tx_bytes
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_dropped
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].tx_dropped
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_errors
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].tx_errors
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_frame_err
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_over_err
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_crc_err
+ '\x00\x00\x00\x00\x00\x00\x00\x01', # entries[1].collisions
+ ])
+ self.assertEquals(expected, msg.pack())
+
+ def test_port_stats_reply_unpack(self):
+ import loxi.of10 as ofp
+ buf = ''.join([
+ '\x01', '\x11', # version/type
+ '\x00\xdc', # length
+ '\x00\x00\x00\x05', # xid
+ '\x00\x04', # stats_type
+ '\x00\x00', # flags
+ '\x00\x01', # entries[0].port_no
+ '\x00\x00\x00\x00\x00\x00' # entries[0].pad
+ '\x00\x00\x00\x00\x00\x00\x00\x38', # entries[0].rx_packets
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].tx_packets
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_bytes
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].tx_bytes
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_dropped
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].tx_dropped
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_errors
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].tx_errors
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_frame_err
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_over_err
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_crc_err
+ '\x00\x00\x00\x00\x00\x00\x00\x05', # entries[0].collisions
+ '\xff\xfe', # entries[1].port_no
+ '\x00\x00\x00\x00\x00\x00' # entries[1].pad
+ '\x00\x00\x00\x00\x00\x00\x00\x01', # entries[1].rx_packets
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].tx_packets
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_bytes
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].tx_bytes
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_dropped
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].tx_dropped
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_errors
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].tx_errors
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_frame_err
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_over_err
+ '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_crc_err
+ '\x00\x00\x00\x00\x00\x00\x00\x01', # entries[1].collisions
+ ])
+ msg = ofp.message.port_stats_reply.unpack(buf)
+ self.assertEquals(ofp.OFPST_PORT, msg.stats_type)
+ self.assertEquals(2, len(msg.entries))
+
+ sample_flow_stats_reply_buf = ''.join([
+ '\x01', '\x11', # version/type
+ '\x00\xe4', # length
+ '\x00\x00\x00\x06', # xid
+ '\x00\x01', # stats_type
+ '\x00\x00', # flags
+ '\x00\x68', # entries[0].length
+ '\x03', # entries[0].table_id
+ '\x00', # entries[0].pad
+ '\x00\x3f\xff\xff', # entries[0].match.wildcards
+ '\x00' * 36, # remaining match fields
+ '\x00\x00\x00\x01', # entries[0].duration_sec
+ '\x00\x00\x00\x02', # entries[0].duration_nsec
+ '\x00\x64', # entries[0].priority
+ '\x00\x05', # entries[0].idle_timeout
+ '\x00\x0a', # entries[0].hard_timeout
+ '\x00' * 6, # entries[0].pad2
+ '\x01\x23\x45\x67\x89\xab\xcd\xef', # entries[0].cookie
+ '\x00\x00\x00\x00\x00\x00\x00\x0a', # entries[0].packet_count
+ '\x00\x00\x00\x00\x00\x00\x03\xe8', # entries[0].byte_count
+ '\x00\x00', # entries[0].actions[0].type
+ '\x00\x08', # entries[0].actions[0].len
+ '\x00\x01', # entries[0].actions[0].port
+ '\x00\x00', # entries[0].actions[0].max_len
+ '\x00\x00', # entries[0].actions[1].type
+ '\x00\x08', # entries[0].actions[1].len
+ '\x00\x02', # entries[0].actions[1].port
+ '\x00\x00', # entries[0].actions[1].max_len
+ '\x00\x70', # entries[1].length
+ '\x04', # entries[1].table_id
+ '\x00', # entries[1].pad
+ '\x00\x3f\xff\xff', # entries[1].match.wildcards
+ '\x00' * 36, # remaining match fields
+ '\x00\x00\x00\x01', # entries[1].duration_sec
+ '\x00\x00\x00\x02', # entries[1].duration_nsec
+ '\x00\x64', # entries[1].priority
+ '\x00\x05', # entries[1].idle_timeout
+ '\x00\x0a', # entries[1].hard_timeout
+ '\x00' * 6, # entries[1].pad2
+ '\x01\x23\x45\x67\x89\xab\xcd\xef', # entries[1].cookie
+ '\x00\x00\x00\x00\x00\x00\x00\x0a', # entries[1].packet_count
+ '\x00\x00\x00\x00\x00\x00\x03\xe8', # entries[1].byte_count
+ '\x00\x00', # entries[1].actions[0].type
+ '\x00\x08', # entries[1].actions[0].len
+ '\x00\x01', # entries[1].actions[0].port
+ '\x00\x00', # entries[1].actions[0].max_len
+ '\x00\x00', # entries[1].actions[1].type
+ '\x00\x08', # entries[1].actions[1].len
+ '\x00\x02', # entries[1].actions[1].port
+ '\x00\x00', # entries[1].actions[1].max_len
+ '\x00\x00', # entries[1].actions[2].type
+ '\x00\x08', # entries[1].actions[2].len
+ '\x00\x03', # entries[1].actions[2].port
+ '\x00\x00', # entries[1].actions[2].max_len
+ ])
+
+ def test_flow_stats_reply_pack(self):
+ import loxi.of10 as ofp
+ msg = ofp.message.flow_stats_reply(xid=6, flags=0, entries=[
+ ofp.flow_stats_entry(table_id=3,
+ match=ofp.match(),
+ duration_sec=1,
+ duration_nsec=2,
+ priority=100,
+ idle_timeout=5,
+ hard_timeout=10,
+ cookie=0x0123456789abcdef,
+ packet_count=10,
+ byte_count=1000,
+ actions=[ofp.action.output(port=1),
+ ofp.action.output(port=2)]),
+ ofp.flow_stats_entry(table_id=4,
+ match=ofp.match(),
+ duration_sec=1,
+ duration_nsec=2,
+ priority=100,
+ idle_timeout=5,
+ hard_timeout=10,
+ cookie=0x0123456789abcdef,
+ packet_count=10,
+ byte_count=1000,
+ actions=[ofp.action.output(port=1),
+ ofp.action.output(port=2),
+ ofp.action.output(port=3)])])
+ self.assertEquals(self.sample_flow_stats_reply_buf, msg.pack())
+
+ def test_flow_stats_reply_unpack(self):
+ import loxi.of10 as ofp
+ msg = ofp.message.flow_stats_reply.unpack(self.sample_flow_stats_reply_buf)
+ self.assertEquals(ofp.OFPST_FLOW, msg.stats_type)
+ self.assertEquals(2, len(msg.entries))
+ self.assertEquals(2, len(msg.entries[0].actions))
+ self.assertEquals(3, len(msg.entries[1].actions))
+
+ def test_flow_add_show(self):
+ import loxi.of10 as ofp
+ expected = """\
+flow_add {
+ xid = None,
+ match = match_v1 {
+ wildcards = OFPFW_DL_SRC|OFPFW_DL_DST,
+ in_port = 3,
+ eth_src = 01:23:45:67:89:ab,
+ eth_dst = cd:ef:01:23:45:67,
+ vlan_vid = 0x0,
+ vlan_pcp = 0x0,
+ pad1 = 0x0,
+ eth_type = 0x0,
+ ip_dscp = 0x0,
+ ip_proto = 0x0,
+ pad2 = [ 0, 0 ],
+ ipv4_src = 192.168.3.127,
+ ipv4_dst = 255.255.255.255,
+ tcp_src = 0x0,
+ tcp_dst = 0x0
+ },
+ cookie = 0x0,
+ idle_timeout = 0x0,
+ hard_timeout = 0x0,
+ priority = 0x0,
+ buffer_id = 0x0,
+ out_port = 0,
+ flags = 0x0,
+ actions = [
+ output { port = OFPP_FLOOD, max_len = 0x0 },
+ nicira_dec_ttl { pad = 0x0, pad2 = 0x0 },
+ bsn_set_tunnel_dst { dst = 0x0 }
+ ]
+}"""
+ msg = ofp.message.flow_add(
+ match=ofp.match(
+ wildcards=ofp.OFPFW_DL_SRC|ofp.OFPFW_DL_DST,
+ in_port=3,
+ ipv4_src=0xc0a8037f,
+ ipv4_dst=0xffffffff,
+ eth_src=[0x01, 0x23, 0x45, 0x67, 0x89, 0xab],
+ eth_dst=[0xcd, 0xef, 0x01, 0x23, 0x45, 0x67]),
+ actions=[
+ ofp.action.output(port=ofp.OFPP_FLOOD),
+ ofp.action.nicira_dec_ttl(),
+ ofp.action.bsn_set_tunnel_dst()])
+ self.assertEquals(msg.show(), expected)
+
+ sample_packet_out_buf = ''.join([
+ '\x01', '\x0d', # version/type
+ '\x00\x23', # length
+ '\x12\x34\x56\x78', # xid
+ '\xab\xcd\xef\x01', # buffer_id
+ '\xff\xfe', # in_port
+ '\x00\x10', # actions_len
+ '\x00\x00', # actions[0].type
+ '\x00\x08', # actions[0].len
+ '\x00\x01', # actions[0].port
+ '\x00\x00', # actions[0].max_len
+ '\x00\x00', # actions[1].type
+ '\x00\x08', # actions[1].len
+ '\x00\x02', # actions[1].port
+ '\x00\x00', # actions[1].max_len
+ 'abc' # data
+ ])
+
+ def test_packet_out_pack(self):
+ import loxi.of10 as ofp
+ msg = ofp.message.packet_out(
+ xid=0x12345678,
+ buffer_id=0xabcdef01,
+ in_port=ofp.OFPP_LOCAL,
+ actions=[
+ ofp.action.output(port=1),
+ ofp.action.output(port=2)],
+ data='abc')
+ self.assertEquals(self.sample_packet_out_buf, msg.pack())
+
+ def test_packet_out_unpack(self):
+ import loxi.of10 as ofp
+ msg = ofp.message.packet_out.unpack(self.sample_packet_out_buf)
+ self.assertEquals(0x12345678, msg.xid)
+ self.assertEquals(0xabcdef01, msg.buffer_id)
+ self.assertEquals(ofp.OFPP_LOCAL, msg.in_port)
+ self.assertEquals(2, len(msg.actions))
+ self.assertEquals(1, msg.actions[0].port)
+ self.assertEquals(2, msg.actions[1].port)
+ self.assertEquals('abc', msg.data)
+
+ sample_packet_in_buf = ''.join([
+ '\x01', '\x0a', # version/type
+ '\x00\x15', # length
+ '\x12\x34\x56\x78', # xid
+ '\xab\xcd\xef\x01', # buffer_id
+ '\x00\x09', # total_len
+ '\xff\xfe', # in_port
+ '\x01', # reason
+ '\x00', # pad
+ 'abc', # data
+ ])
+
+ def test_packet_in_pack(self):
+ import loxi.of10 as ofp
+ msg = ofp.message.packet_in(
+ xid=0x12345678,
+ buffer_id=0xabcdef01,
+ total_len=9,
+ in_port=ofp.OFPP_LOCAL,
+ reason=ofp.OFPR_ACTION,
+ data='abc')
+ self.assertEquals(self.sample_packet_in_buf, msg.pack())
+
+ def test_packet_in_unpack(self):
+ import loxi.of10 as ofp
+ msg = ofp.message.packet_in.unpack(self.sample_packet_in_buf)
+ self.assertEquals(0x12345678, msg.xid)
+ self.assertEquals(0xabcdef01, msg.buffer_id)
+ self.assertEquals(9, msg.total_len)
+ self.assertEquals(ofp.OFPP_LOCAL, msg.in_port)
+ self.assertEquals(ofp.OFPR_ACTION, msg.reason)
+ self.assertEquals('abc', msg.data)
+
+ sample_queue_get_config_reply_buf = ''.join([
+ '\x01', '\x15', # version/type
+ '\x00\x50', # length
+ '\x12\x34\x56\x78', # xid
+ '\xff\xfe', # port
+ '\x00\x00\x00\x00\x00\x00', # pad
+ '\x00\x00\x00\x01', # queues[0].queue_id
+ '\x00\x18', # queues[0].len
+ '\x00\x00', # queues[0].pad
+ '\x00\x01', # queues[0].properties[0].type
+ '\x00\x10', # queues[0].properties[0].length
+ '\x00\x00\x00\x00', # queues[0].properties[0].pad
+ '\x00\x05', # queues[0].properties[0].rate
+ '\x00\x00\x00\x00\x00\x00', # queues[0].properties[0].pad2
+ '\x00\x00\x00\x02', # queues[1].queue_id
+ '\x00\x28', # queues[1].len
+ '\x00\x00', # queues[1].pad
+ '\x00\x01', # queues[1].properties[0].type
+ '\x00\x10', # queues[1].properties[0].length
+ '\x00\x00\x00\x00', # queues[1].properties[0].pad
+ '\x00\x06', # queues[1].properties[0].rate
+ '\x00\x00\x00\x00\x00\x00', # queues[1].properties[0].pad2
+ '\x00\x01', # queues[1].properties[1].type
+ '\x00\x10', # queues[1].properties[1].length
+ '\x00\x00\x00\x00', # queues[1].properties[1].pad
+ '\x00\x07', # queues[1].properties[1].rate
+ '\x00\x00\x00\x00\x00\x00', # queues[1].properties[1].pad2
+ ])
+
+ def test_queue_get_config_reply_pack(self):
+ import loxi.of10 as ofp
+ msg = ofp.message.queue_get_config_reply(
+ xid=0x12345678,
+ port=ofp.OFPP_LOCAL,
+ queues=[
+ ofp.packet_queue(queue_id=1, properties=[
+ ofp.queue_prop_min_rate(rate=5)]),
+ ofp.packet_queue(queue_id=2, properties=[
+ ofp.queue_prop_min_rate(rate=6),
+ ofp.queue_prop_min_rate(rate=7)])])
+ self.assertEquals(self.sample_queue_get_config_reply_buf, msg.pack())
+
+ def test_queue_get_config_reply_unpack(self):
+ import loxi.of10 as ofp
+ msg = ofp.message.queue_get_config_reply.unpack(self.sample_queue_get_config_reply_buf)
+ self.assertEquals(ofp.OFPP_LOCAL, msg.port)
+ self.assertEquals(msg.queues[0].queue_id, 1)
+ self.assertEquals(msg.queues[0].properties[0].rate, 5)
+ self.assertEquals(msg.queues[1].queue_id, 2)
+ self.assertEquals(msg.queues[1].properties[0].rate, 6)
+ self.assertEquals(msg.queues[1].properties[1].rate, 7)
+
+class TestParse(unittest.TestCase):
+ def test_parse_header(self):
+ import loxi
+ import loxi.of10 as ofp
+
+ msg_ver, msg_type, msg_len, msg_xid = ofp.message.parse_header("\x01\x04\xAF\xE8\x12\x34\x56\x78")
+ self.assertEquals(1, msg_ver)
+ self.assertEquals(4, msg_type)
+ self.assertEquals(45032, msg_len)
+ self.assertEquals(0x12345678, msg_xid)
+
+ with self.assertRaisesRegexp(loxi.ProtocolError, "too short"):
+ ofp.message.parse_header("\x01\x04\xAF\xE8\x12\x34\x56")
+
+ def test_parse_message(self):
+ import loxi
+ import loxi.of10 as ofp
+
+ buf = "\x01\x00\x00\x08\x12\x34\x56\x78"
+ msg = ofp.message.parse_message(buf)
+ assert(msg.xid == 0x12345678)
+
+ # Get a list of all message classes
+ test_klasses = [x for x in ofp.message.__dict__.values()
+ if type(x) == type
+ and issubclass(x, ofp.message.Message)
+ and x != ofp.message.Message]
+
+ for klass in test_klasses:
+ self.assertIsInstance(ofp.message.parse_message(klass(xid=1).pack()), klass)
+
+class TestUtils(unittest.TestCase):
+ def test_unpack_array(self):
+ import loxi
+ import loxi.of10.util as util
+
+ a = util.unpack_array(str, 3, "abcdefghi")
+ self.assertEquals(['abc', 'def', 'ghi'], a)
+
+ with self.assertRaisesRegexp(loxi.ProtocolError, "invalid array length"):
+ util.unpack_array(str, 3, "abcdefgh")
+
+ def test_pretty_wildcards(self):
+ import loxi.of10 as ofp
+ self.assertEquals("OFPFW_ALL", ofp.util.pretty_wildcards(ofp.OFPFW_ALL))
+ self.assertEquals("0", ofp.util.pretty_wildcards(0))
+ self.assertEquals("OFPFW_DL_SRC|OFPFW_DL_DST",
+ ofp.util.pretty_wildcards(ofp.OFPFW_DL_SRC|ofp.OFPFW_DL_DST))
+ self.assertEquals("OFPFW_NW_SRC_MASK&0x2000",
+ ofp.util.pretty_wildcards(ofp.OFPFW_NW_SRC_ALL))
+ self.assertEquals("OFPFW_NW_SRC_MASK&0x1a00",
+ ofp.util.pretty_wildcards(0x00001a00))
+ self.assertEquals("OFPFW_IN_PORT|0x80000000",
+ ofp.util.pretty_wildcards(ofp.OFPFW_IN_PORT|0x80000000))
+
+class TestAll(unittest.TestCase):
+ """
+ Round-trips every class through serialization/deserialization.
+ Not a replacement for handcoded tests because it only uses the
+ default member values.
+ """
+
+ def setUp(self):
+ import loxi.of10 as ofp
+ mods = [ofp.action,ofp.message,ofp.common]
+ self.klasses = [klass for mod in mods
+ for klass in mod.__dict__.values()
+ if hasattr(klass, 'show')]
+ self.klasses.sort(key=lambda x: str(x))
+
+ def test_serialization(self):
+ import loxi.of10 as ofp
+ expected_failures = []
+ for klass in self.klasses:
+ def fn():
+ obj = klass()
+ if hasattr(obj, "xid"): obj.xid = 42
+ buf = obj.pack()
+ obj2 = klass.unpack(buf)
+ self.assertEquals(obj, obj2)
+ if klass in expected_failures:
+ self.assertRaises(Exception, fn)
+ else:
+ fn()
+
+ def test_show(self):
+ import loxi.of10 as ofp
+ expected_failures = []
+ for klass in self.klasses:
+ def fn():
+ obj = klass()
+ if hasattr(obj, "xid"): obj.xid = 42
+ obj.show()
+ if klass in expected_failures:
+ self.assertRaises(Exception, fn)
+ else:
+ fn()
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/py_gen/util.py b/py_gen/util.py
new file mode 100644
index 0000000..fbb2825
--- /dev/null
+++ b/py_gen/util.py
@@ -0,0 +1,79 @@
+# 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.
+
+"""
+Utilities for generating the target Python code
+"""
+
+import os
+import of_g
+import loxi_front_end.type_maps as type_maps
+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)
+
+def render_static(out, name):
+ utils.render_static(out, name, [templates_dir])
+
+def lookup_unified_class(cls, version):
+ unified_class = of_g.unified[cls][version]
+ if "use_version" in unified_class: # deref version ref
+ ref_version = unified_class["use_version"]
+ unified_class = of_g.unified[cls][ref_version]
+ return unified_class
+
+def primary_wire_type(cls, version):
+ if cls in type_maps.stats_reply_list:
+ return type_maps.type_val[("of_stats_reply", version)]
+ elif cls in type_maps.stats_request_list:
+ return type_maps.type_val[("of_stats_request", version)]
+ elif cls in type_maps.flow_mod_list:
+ return type_maps.type_val[("of_flow_mod", version)]
+ elif (cls, version) in type_maps.type_val:
+ return type_maps.type_val[(cls, version)]
+ elif type_maps.message_is_extension(cls, version):
+ return type_maps.type_val[("of_experimenter", version)]
+ elif type_maps.action_is_extension(cls, version):
+ return type_maps.type_val[("of_action_experimenter", version)]
+ elif type_maps.action_id_is_extension(cls, version):
+ return type_maps.type_val[("of_action_id_experimenter", version)]
+ elif type_maps.instruction_is_extension(cls, version):
+ return type_maps.type_val[("of_instruction_experimenter", version)]
+ elif type_maps.queue_prop_is_extension(cls, version):
+ return type_maps.type_val[("of_queue_prop_experimenter", version)]
+ elif type_maps.table_feature_prop_is_extension(cls, version):
+ return type_maps.type_val[("of_table_feature_prop_experimenter", version)]
+ else:
+ raise ValueError
+
+def constant_for_value(version, group, value):
+ return (["const." + v["ofp_name"] for k, v in of_g.identifiers.items()
+ if k in of_g.identifiers_by_group[group] and
+ eval(v["values_by_version"].get(version, "None")) == value] or [value])[0]