loxi_ir: Moved to its own package

to group ir and related functionality. Also introduce a dedicated
frontend_ir model, and adds a unit test
diff --git a/loxi_ir/ir_offset.py b/loxi_ir/ir_offset.py
new file mode 100644
index 0000000..24589b2
--- /dev/null
+++ b/loxi_ir/ir_offset.py
@@ -0,0 +1,196 @@
+## List of mixed data types
+#
+# This is a list of data types which require special treatment
+# because the underlying datatype has changed between versions.
+# The main example is port which went from 16 to 32 bits.  We
+# define per-version accessors for these types and those are
+# used in place of the normal ones.
+#
+# The wire protocol number is used to identify versions.  For now,
+# the value is the name of the type to use for that version
+#
+# This is the map between the external type (like of_port_no_t)
+# which is used by customers of this code and the internal
+# datatypes (like uint16_t) that appear on the wire for a
+# particular version.
+#
+from collections import namedtuple
+import logging
+
+import loxi_front_end.frontend_ir as fe
+import loxi_ir.ir
+
+ofp_constants = dict(
+    OF_MAX_TABLE_NAME_LEN = 32,
+    OF_MAX_PORT_NAME_LEN  = 16,
+    OF_ETH_ALEN = 6,
+    OF_DESC_STR_LEN   = 256,
+    OF_SERIAL_NUM_LEN = 32
+)
+
+
+of_mixed_types = dict(
+    of_port_no_t = {
+        1: "uint16_t",
+        2: "uint32_t",
+        3: "uint32_t",
+        4: "uint32_t",
+        "short_name":"port_no"
+        },
+    of_port_desc_t = {
+        1: "of_port_desc_t",
+        2: "of_port_desc_t",
+        3: "of_port_desc_t",
+        4: "of_port_desc_t",
+        "short_name":"port_desc"
+        },
+    of_bsn_vport_t = {
+        1: "of_bsn_vport_t",
+        2: "of_bsn_vport_t",
+        3: "of_bsn_vport_t",
+        4: "of_bsn_vport_t",
+        "short_name":"bsn_vport"
+        },
+    of_fm_cmd_t = { # Flow mod command went from u16 to u8
+        1: "uint16_t",
+        2: "uint8_t",
+        3: "uint8_t",
+        4: "uint8_t",
+        "short_name":"fm_cmd"
+        },
+    of_wc_bmap_t = { # Wildcard bitmap
+        1: "uint32_t",
+        2: "uint32_t",
+        3: "uint64_t",
+        4: "uint64_t",
+        "short_name":"wc_bmap"
+        },
+    of_match_bmap_t = { # Match bitmap
+        1: "uint32_t",
+        2: "uint32_t",
+        3: "uint64_t",
+        4: "uint64_t",
+        "short_name":"match_bmap"
+        },
+    of_match_t = { # Match object
+        1: "of_match_v1_t",
+        2: "of_match_v2_t",
+        3: "of_match_v3_t",
+        4: "of_match_v3_t",  # Currently uses same match as 1.2 (v3).
+        "short_name":"match"
+        },
+)
+
+## basic lengths
+of_base_lengths = dict(
+    char     = (1, True),
+    uint8_t  = (1, True),
+    uint16_t = (2, True),
+    uint32_t = (4, True),
+    uint64_t = (8, True),
+    of_mac_addr_t = (6, True),
+    of_ipv4_t = (4, True),
+    of_ipv6_t = (16, True),
+    of_port_name_t = (ofp_constants["OF_MAX_PORT_NAME_LEN"], True),
+    of_table_name_t = (ofp_constants["OF_MAX_TABLE_NAME_LEN"], True),
+    of_desc_str_t = (ofp_constants["OF_DESC_STR_LEN"], True),
+    of_serial_num_t = (ofp_constants["OF_SERIAL_NUM_LEN"], True),
+    of_match_v1_t = (40, True),
+    of_match_v2_t = (88, True),
+    of_match_v3_t = (8, False),
+    of_octets_t = (0, False),
+    of_bitmap_128_t = (16, True),
+)
+
+def type_dec_to_count_base(m_type):
+    """
+    Resolve a type declaration like uint8_t[4] to a count (4) and base_type
+    (uint8_t)
+
+    @param m_type The string type declaration to process
+    """
+    count = 1
+    chk_ar = m_type.split('[')
+    if len(chk_ar) > 1:
+        count_str = chk_ar[1].split(']')[0]
+        if count_str in ofp_constants:
+            count = ofp_constants[count_str]
+        else:
+            count = int(count_str)
+        base_type = chk_ar[0]
+    else:
+        base_type = m_type
+    return count, base_type
+
+
+LengthInfo = namedtuple("LengthInfo", ("offset", "base_length", "is_fixed_length"))
+
+def calc_lengths(version, fe_class, existing_classes, existing_enums):
+    offset_fixed = True
+    offset = 0
+
+    member_infos = {}
+    for member in fe_class.members:
+        member_offset = offset if offset_fixed else None
+
+        if isinstance(member, fe.OFPadMember):
+            member_base_length = member.length
+            member_fixed_length = True
+        else:
+            m_type = member.oftype
+            name = member.name
+
+            member_base_length = 0
+            if m_type.find("list(") == 0:
+                member_fixed_length = False
+            elif m_type.find("struct") == 0:
+                raise Exception("Error: recursive struct found: {}, {}"
+                                    .format(fe_class.name, name))
+            elif m_type == "octets":
+                member_fixed_length = False
+            else:
+                member_base_length, member_fixed_length = member_length(version, fe_class, member, existing_classes, existing_enums)
+
+        if not member_fixed_length:
+            offset_fixed = False
+
+        member_infos[member] = LengthInfo(member_offset, member_base_length,
+                member_fixed_length)
+        offset += member_base_length
+
+    base_length = offset
+    fixed_length = offset_fixed if not fe_class.virtual else False
+    return (base_length, fixed_length, member_infos)
+
+def member_length(version, fe_class, fe_member, existing_classes, existing_enums):
+    """
+    return the length of an ir member.
+
+    @return tuple (base_length, length_fixed)
+    """
+    count, base_type = type_dec_to_count_base(fe_member.oftype)
+
+    len_update = 0
+    if base_type in of_mixed_types:
+        base_type = of_mixed_types[base_type][version.wire_version]
+
+    base_class = base_type[:-2]
+    if base_class in existing_classes:
+        member_ir_class = existing_classes[base_class]
+        bytes = member_ir_class.base_length
+        length_fixed = member_ir_class.is_fixed_length
+    else:
+        if base_type in existing_enums:
+            enum = existing_enums[base_type]
+            base_type = enum.wire_type
+
+        if base_type in of_base_lengths:
+            bytes, length_fixed = of_base_lengths[base_type]
+        else:
+            raise Exception("Unknown type for {}.{}: {}".format(fe_class.name, fe_member.name, base_type))
+
+    return (count * bytes), length_fixed
+
+def calculate_offsets_and_lengths(protocol):
+    for ir_class in protocol.classes:
+        offset_and_length_for_class(ir_class)