Merge into master from pull request #150:
loci: expose group-mod subtypes (https://github.com/floodlight/loxigen/pull/150)
diff --git a/c_gen/build_of_g.py b/c_gen/build_of_g.py
index 46aedda..2a272ce 100755
--- a/c_gen/build_of_g.py
+++ b/c_gen/build_of_g.py
@@ -317,8 +317,6 @@
         version_name = of_g.of_version_wire2name[wire_version]
 
         for ofclass in protocol.classes:
-            if ofclass.name in ("of_group_add", "of_group_modify", "of_group_delete"):
-                continue
             of_g.ordered_classes[wire_version].append(ofclass.name)
             legacy_members = []
             pad_count = 0
@@ -417,9 +415,6 @@
                 continue
             if type_maps.class_is_virtual(cls):
                 continue
-            # HACK hide of_group subclasses from legacy c backend
-            if ofclass.name in ("of_group_add", "of_group_modify", "of_group_delete"):
-                continue
             subcls = cls[3:]
             val = find_type_value(ofclass, 'type')
             if not val in type_maps.message_types[wire_version].values():
diff --git a/c_gen/c_type_maps.py b/c_gen/c_type_maps.py
index e5c36fc..09ee486 100644
--- a/c_gen/c_type_maps.py
+++ b/c_gen/c_type_maps.py
@@ -75,6 +75,10 @@
                 out.write("    %d%s /* %s */\n" %
                           (type_maps.type_val[("of_flow_mod", version)],
                            comma, cls))
+            elif cls in type_maps.group_mod_list and version > 1:
+                out.write("    %d%s /* %s */\n" %
+                          (type_maps.type_val[("of_group_mod", version)],
+                           comma, cls))
             elif (cls, version) in type_maps.type_val and \
                     type_maps.type_val[(cls, version)] != type_maps.invalid_type:
                 out.write("    %d%s /* %s */\n" %
@@ -261,6 +265,9 @@
     gen_type_to_object_id(out, "hello_elem_type_to_id", "OF_HELLO_ELEM",
                           "OF_HELLO_ELEM_%s", type_maps.hello_elem_types,
                           max_type_value)
+    gen_type_to_object_id(out, "group_mod_type_to_id", "OF_GROUP_MOD",
+                          "OF_GROUP_%s", type_maps.group_mod_types,
+                          max_type_value)
 
     # FIXME:  Multipart re-organization
     gen_type_to_object_id(out, "stats_request_type_to_id", "OF_STATS_REQUEST",
@@ -504,6 +511,7 @@
     uint16_t err_type;
     uint8_t flow_mod_cmd;
     uint32_t experimenter, subtype;
+    uint16_t group_mod_cmd;
 
     if (length < OF_MESSAGE_MIN_LENGTH) {
         return OF_OBJECT_INVALID;
@@ -570,6 +578,14 @@
         obj_id = of_error_msg_to_object_id(err_type, ver);
     }
 
+    if (obj_id == OF_GROUP_MOD) {
+        if (length < OF_MESSAGE_MIN_GROUP_MOD_LENGTH) {
+            return OF_OBJECT_INVALID;
+        }
+        group_mod_cmd = of_message_group_mod_command_get(msg);
+        obj_id = of_group_mod_to_object_id(group_mod_cmd, ver);
+    }
+
     return obj_id;
 }
 """
@@ -692,6 +708,11 @@
     out.write(map_template %
               dict(name="flow_mod", u_name="FLOW_MOD", ar_len=ar_len))
 
+    ar_len = type_maps.type_array_len(type_maps.group_mod_types,
+                                      max_type_value)
+    out.write(map_template %
+              dict(name="group_mod", u_name="GROUP_MOD", ar_len=ar_len))
+
     # OXM
     ar_len = type_maps.type_array_len(type_maps.oxm_types, max_type_value)
     out.write("""
@@ -947,6 +968,48 @@
 
 """)
 
+    ################################################################
+    # Generate object ID to the group mod sub-type map
+    ################################################################
+
+    out.write("""
+/**
+ * Map an object ID to a group-mod command value
+ * @param id An object ID
+ * @return The wire value for the group-mod command
+ * @return -1 if not supported for this version
+ * @return -1 if id is not a specific stats type ID
+ *
+ * Note that the value is returned as a signed integer.  So -1 is
+ * an error code, while 0xffff is the usual "experimenter" code.
+ */
+
+static inline int
+of_object_to_group_mod_command(of_object_id_t id, of_version_t version)
+{
+    if (!OF_VERSION_OKAY(version)) {
+        return -1;
+    }
+    switch (id) {""")
+    group_mod_names = set()
+    for ver in of_g.of_version_range:
+        for name in type_maps.group_mod_types[ver]:
+            group_mod_names.add(name)
+    for name in group_mod_names:
+        out.write("""
+    case OF_GROUP_%(name)s:
+        if (OF_GROUP_MOD_COMMAND_%(name)s_SUPPORTED(version))
+            return OF_GROUP_MOD_COMMAND_%(name)s_BY_VERSION(version);
+        break;""" % {"name": name.upper()})
+    out.write("""
+    default:
+        break;
+    }
+    return -1; /* Not recognized as group mod type object for this version */
+}
+
+""")
+
 def gen_type_maps_header(out):
     """
     Generate various header file declarations for type maps
@@ -1099,6 +1162,10 @@
         /* It's a flow mod obj */
         of_message_flow_mod_command_set(msg, ver, type);
     }
+    if ((type = of_object_to_group_mod_command(id, ver)) >= 0) {
+        /* It's a group mod obj */
+        of_message_group_mod_command_set(msg, type);
+    }
     if (of_object_id_is_extension(id, ver)) {
         uint32_t val32;
 
diff --git a/c_gen/of_g_legacy.py b/c_gen/of_g_legacy.py
index adf5c2b..80d922c 100644
--- a/c_gen/of_g_legacy.py
+++ b/c_gen/of_g_legacy.py
@@ -99,7 +99,7 @@
 ## These members do not get normal accessors
 
 skip_members = ["version", "type", "length", "err_type", "stats_type", "len",
-                "type_len", "actions_len", "_command"]
+                "type_len", "actions_len", "_command", "command"]
 
 ## Some OpenFlow string length constants
 #
diff --git a/c_gen/templates/of_message.h b/c_gen/templates/of_message.h
index c1b6785..befda2f 100644
--- a/c_gen/templates/of_message.h
+++ b/c_gen/templates/of_message.h
@@ -48,11 +48,13 @@
 #define OF_MESSAGE_ERROR_TYPE_OFFSET 8
 #define OF_MESSAGE_STATS_TYPE_OFFSET 8
 #define OF_MESSAGE_FLOW_MOD_COMMAND_OFFSET(version) ((version) == 1 ? 56 : 25)
+#define OF_MESSAGE_GROUP_MOD_COMMAND_OFFSET 8
 
 #define OF_MESSAGE_MIN_LENGTH 8
 #define OF_MESSAGE_MIN_STATS_LENGTH (OF_MESSAGE_STATS_TYPE_OFFSET + 2)
 #define OF_MESSAGE_MIN_ERROR_LENGTH (OF_MESSAGE_ERROR_TYPE_OFFSET + 4)
 #define OF_MESSAGE_MIN_FLOW_MOD_LENGTH(version)  ((version) == 1 ? 57 : 26)
+#define OF_MESSAGE_MIN_GROUP_MOD_LENGTH (OF_MESSAGE_GROUP_MOD_COMMAND_OFFSET + 2)
 
 #define OF_MESSAGE_EXPERIMENTER_ID_OFFSET 8
 #define OF_MESSAGE_EXPERIMENTER_SUBTYPE_OFFSET 12
@@ -312,4 +314,23 @@
     buf_u32_set(msg + OF_MESSAGE_STATS_EXPERIMENTER_SUBTYPE_OFFSET, subtype);
 }
 
+/**
+ * @brief Get/set group mod command of a message
+ * @param msg Pointer to the message buffer of sufficient length
+ * @param subtype Data for set operation
+ * @returns get returns command in host order
+ */
+
+static inline uint16_t
+of_message_group_mod_command_get(of_message_t msg) {
+    uint16_t val;
+    buf_u16_get(msg + OF_MESSAGE_GROUP_MOD_COMMAND_OFFSET, &val);
+    return val;
+}
+
+static inline void
+of_message_group_mod_command_set(of_message_t msg, uint16_t command) {
+    buf_u16_set(msg + OF_MESSAGE_GROUP_MOD_COMMAND_OFFSET, command);
+}
+
 #endif /* _OF_MESSAGE_H_ */
diff --git a/c_gen/translation.py b/c_gen/translation.py
index ef6c11b..fb64fa9 100644
--- a/c_gen/translation.py
+++ b/c_gen/translation.py
@@ -89,7 +89,7 @@
         dict(OFPM_ = "OF_METER_"),
         dict(OFPXMC_ = "OF_OXM_CLASS_"),
         dict(OFPVID_ = "OF_VLAN_TAG_"),
-        dict(OFPGC_ = "OF_GROUP_"),
+        dict(OFPGC_ = "OF_GROUP_MOD_COMMAND_"),
         dict(OFPGT_ = "OF_GROUP_TYPE_"),
         dict(OFPG_ = "OF_GROUP_"),
         dict(OFPET_ = "OF_ERROR_TYPE_"),
diff --git a/c_gen/type_maps.py b/c_gen/type_maps.py
index 6ae0b11..ae5f481 100644
--- a/c_gen/type_maps.py
+++ b/c_gen/type_maps.py
@@ -156,7 +156,7 @@
     if loxi_utils.class_is_list(cls):
         return True
     # TODO get this from the input file when we have virtual class syntax
-    if cls in ["of_flow_mod", "of_stats_request", "of_stats_reply", "of_error_msg", "of_bsn_header", "of_nicira_header", "of_action_bsn", "of_action_nicira", "of_action_id_bsn", "of_action_id_nicira", "of_bsn_stats_request", "of_bsn_stats_reply", "of_experimenter_stats_request", "of_experimenter_stats_reply", "of_instruction_experimenter", "of_instruction_bsn"]:
+    if cls in ["of_flow_mod", "of_stats_request", "of_stats_reply", "of_error_msg", "of_bsn_header", "of_nicira_header", "of_action_bsn", "of_action_nicira", "of_action_id_bsn", "of_action_id_nicira", "of_bsn_stats_request", "of_bsn_stats_reply", "of_experimenter_stats_request", "of_experimenter_stats_reply", "of_instruction_experimenter", "of_instruction_bsn", "of_group_mod"]:
         return True
     return False
 
@@ -182,6 +182,7 @@
         error_msg               = 1,
         experimenter            = 4,
         flow_mod                = 14,
+        group_mod               = 15,
         stats_request           = 18,
         stats_reply             = 19,
         ),
@@ -191,6 +192,7 @@
         error_msg               = 1,
         experimenter            = 4,
         flow_mod                = 14,
+        group_mod               = 15,
         stats_request           = 18,
         stats_reply             = 19,
         ),
@@ -200,6 +202,7 @@
         error_msg               = 1,
         experimenter            = 4,
         flow_mod                = 14,
+        group_mod               = 15,
         stats_request           = 18,  # FIXME Multipart
         stats_reply             = 19,
         )
@@ -355,6 +358,32 @@
         )
     }
 
+group_mod_types = {
+    # version 1.0
+    of_g.VERSION_1_0:dict(),
+
+    # version 1.1
+    of_g.VERSION_1_1:dict(
+        add = 0,
+        modify = 1,
+        delete = 2
+        ),
+
+    # version 1.2
+    of_g.VERSION_1_2:dict(
+        add = 0,
+        modify = 1,
+        delete = 2
+        ),
+
+    # version 1.3
+    of_g.VERSION_1_3:dict(
+        add = 0,
+        modify = 1,
+        delete = 2
+        )
+    }
+
 ##
 # These are the objects whose length is specified by an external
 # reference, specifically another data member in the class.
@@ -513,6 +542,12 @@
     "of_experimenter_error_msg"
 ]
 
+group_mod_list = [
+    "of_group_add",
+    "of_group_modify",
+    "of_group_delete",
+]
+
 def sub_class_map(base_type, version):
     """
     Returns an iterable object giving the instance nameys and subclass types