pyloxi: support field length members

These are members whose value is the length of another member. This is used in
OpenFlow for variable length members in the middle of a message. Variable
length members at the end do not need a field length member because their
length is implicit. The field length members must come before, but not
necessarily immediately before, the field they refer to.

The only place this was used in OF 1.0 was in packet_out. Now that this is
gnerically supported we can remove the special case code for packet_out.
diff --git a/py_gen/codegen.py b/py_gen/codegen.py
index 01dd782..82bf772 100644
--- a/py_gen/codegen.py
+++ b/py_gen/codegen.py
@@ -37,9 +37,18 @@
                                  'min_length', 'is_fixed_length'])
 Member = namedtuple('Member', ['name', 'oftype', 'offset', 'skip'])
 LengthMember = namedtuple('LengthMember', ['name', 'oftype', 'offset'])
+FieldLengthMember = namedtuple('FieldLengthMember', ['name', 'oftype', 'offset', 'field_name'])
 TypeMember = namedtuple('TypeMember', ['name', 'oftype', 'offset', 'value'])
 PadMember = namedtuple('PadMember', ['offset', 'length'])
 
+# XXX move to frontend
+field_length_members = {
+    ('of_packet_out', 1, 'actions_len') : 'actions',
+    ('of_packet_out', 2, 'actions_len') : 'actions',
+    ('of_packet_out', 3, 'actions_len') : 'actions',
+    ('of_packet_out', 4, 'actions_len') : 'actions',
+}
+
 def get_type_values(cls, version):
     """
     Returns a map from the name of the type member to its value.
@@ -112,6 +121,12 @@
                 members.append(LengthMember(name=member['name'],
                                             offset=member['offset'],
                                             oftype=oftype.OFType(member['m_type'], version)))
+            elif (cls, version, member['name']) in field_length_members:
+                field_name = field_length_members[(cls, version, member['name'])]
+                members.append(FieldLengthMember(name=member['name'],
+                                                 offset=member['offset'],
+                                                 oftype=oftype.OFType(member['m_type'], version),
+                                                 field_name=field_name))
             elif member['name'] in type_values:
                 members.append(TypeMember(name=member['name'],
                                           offset=member['offset'],
diff --git a/py_gen/templates/_pack.py b/py_gen/templates/_pack.py
index 66ee763..e853833 100644
--- a/py_gen/templates/_pack.py
+++ b/py_gen/templates/_pack.py
@@ -26,19 +26,30 @@
 :: # under the EPL.
 ::
 :: # TODO coalesce format strings
-:: from py_gen.codegen import Member, LengthMember, TypeMember, PadMember
+:: from py_gen.codegen import Member, LengthMember, FieldLengthMember, TypeMember, PadMember
 :: length_member = None
 :: length_member_index = None
+:: field_length_members = {}
+:: field_length_indexes = {}
 :: index = 0
 :: for m in ofclass.members:
 ::     if type(m) == LengthMember:
 ::         length_member = m
 ::         length_member_index = index
-        packed.append(${m.oftype.gen_pack_expr('0')}) # placeholder for ${m.name} at index ${length_member_index}
+        packed.append(${m.oftype.gen_pack_expr('0')}) # placeholder for ${m.name} at index ${index}
+::     elif type(m) == FieldLengthMember:
+::         field_length_members[m.field_name] = m
+::         field_length_indexes[m.field_name] = index
+        packed.append(${m.oftype.gen_pack_expr('0')}) # placeholder for ${m.name} at index ${index}
 ::     elif type(m) == PadMember:
         packed.append('\x00' * ${m.length})
 ::     else:
         packed.append(${m.oftype.gen_pack_expr('self.' + m.name)})
+::         if m.name in field_length_members:
+::             field_length_member = field_length_members[m.name]
+::             field_length_index = field_length_indexes[m.name]
+        packed[${field_length_index}] = ${field_length_member.oftype.gen_pack_expr('len(packed[-1])')}
+::         #endif
 ::     #endif
 ::     index += 1
 :: #endfor
diff --git a/py_gen/templates/_pack_packet_out.py b/py_gen/templates/_pack_packet_out.py
deleted file mode 100644
index ad8b827..0000000
--- a/py_gen/templates/_pack_packet_out.py
+++ /dev/null
@@ -1,39 +0,0 @@
-:: # 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/_unpack.py b/py_gen/templates/_unpack.py
index 8be8254..6bf0db6 100644
--- a/py_gen/templates/_unpack.py
+++ b/py_gen/templates/_unpack.py
@@ -26,23 +26,29 @@
 :: # under the EPL.
 ::
 :: # TODO coalesce format strings
-:: from py_gen.codegen import Member, LengthMember, TypeMember, PadMember
+:: from py_gen.codegen import Member, LengthMember, FieldLengthMember, TypeMember, PadMember
         if type(buf) == loxi.generic_util.OFReader:
             reader = buf
         else:
             reader = loxi.generic_util.OFReader(buf)
+:: field_length_members = {}
 :: for m in ofclass.members:
 ::     if type(m) == PadMember:
         reader.skip(${m.length})
-::         continue
-::     #endif
-::     unpack_expr = m.oftype.gen_unpack_expr('reader')
-::     if type(m) == LengthMember:
-        _${m.name} = ${unpack_expr}
+::     elif type(m) == LengthMember:
+        _${m.name} = ${m.oftype.gen_unpack_expr('reader')}
+::     elif type(m) == FieldLengthMember:
+::         field_length_members[m.field_name] = m
+        _${m.name} = ${m.oftype.gen_unpack_expr('reader')}
 ::     elif type(m) == TypeMember:
-        _${m.name} = ${unpack_expr}
+        _${m.name} = ${m.oftype.gen_unpack_expr('reader')}
         assert(_${m.name} == ${m.value})
-::     else:
-        obj.${m.name} = ${unpack_expr}
+::     elif type(m) == Member:
+::         if m.name in field_length_members:
+::             reader_expr = 'reader.slice(_%s)' % field_length_members[m.name].name
+::         else:
+::             reader_expr = 'reader'
+::         #endif
+        obj.${m.name} = ${m.oftype.gen_unpack_expr(reader_expr)}
 ::     #endif
 :: #endfor
diff --git a/py_gen/templates/_unpack_packet_out.py b/py_gen/templates/_unpack_packet_out.py
deleted file mode 100644
index f7f9683..0000000
--- a/py_gen/templates/_unpack_packet_out.py
+++ /dev/null
@@ -1,42 +0,0 @@
-:: # 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.
-::
-        if type(buf) == loxi.generic_util.OFReader:
-            reader = buf
-        else:
-            reader = loxi.generic_util.OFReader(buf)
-        _version = reader.read('!B')[0]
-        assert(_version == const.OFP_VERSION)
-        _type = reader.read('!B')[0]
-        assert(_type == const.OFPT_PACKET_OUT)
-        _length = reader.read('!H')[0]
-        obj.xid = reader.read('!L')[0]
-        obj.buffer_id = reader.read('!L')[0]
-        obj.in_port = reader.read('!H')[0]
-        _actions_len = reader.read('!H')[0]
-        obj.actions = action.unpack_list(reader.slice(_actions_len))
-        obj.data = str(reader.read_all())
diff --git a/py_gen/templates/message.py b/py_gen/templates/message.py
index d501854..86fb3bf 100644
--- a/py_gen/templates/message.py
+++ b/py_gen/templates/message.py
@@ -64,22 +64,14 @@
 
     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):