Implement Openflow 1.4 Vacancy Events for OFPT_TABLE_MOD.
[cascardo/ovs.git] / lib / ofp-print.c
index d76134f..1f22dd7 100644 (file)
@@ -52,9 +52,6 @@
 
 static void ofp_print_queue_name(struct ds *string, uint32_t port);
 static void ofp_print_error(struct ds *, enum ofperr);
-static void ofp_print_table_features(struct ds *,
-                                     const struct ofputil_table_features *,
-                                     const struct ofputil_table_stats *);
 
 /* Returns a string that represents the contents of the Ethernet frame in the
  * 'len' bytes starting at 'data'.  The caller must free the returned string.*/
@@ -993,6 +990,18 @@ ofputil_put_eviction_flags(struct ds *string, uint32_t eviction_flags)
     }
 }
 
+static const char *
+ofputil_table_vacancy_to_string(enum ofputil_table_vacancy vacancy)
+{
+    switch (vacancy) {
+    case OFPUTIL_TABLE_VACANCY_DEFAULT: return "default";
+    case OFPUTIL_TABLE_VACANCY_ON: return "on";
+    case OFPUTIL_TABLE_VACANCY_OFF: return "off";
+    default: return "***error***";
+    }
+
+}
+
 static void
 ofp_print_table_mod(struct ds *string, const struct ofp_header *oh)
 {
@@ -1023,6 +1032,27 @@ ofp_print_table_mod(struct ds *string, const struct ofp_header *oh)
         ds_put_cstr(string, "eviction_flags=");
         ofputil_put_eviction_flags(string, pm.eviction_flags);
     }
+    if (pm.vacancy != OFPUTIL_TABLE_VACANCY_DEFAULT) {
+        ds_put_format(string, ", vacancy=%s",
+                      ofputil_table_vacancy_to_string(pm.vacancy));
+        if (pm.vacancy == OFPUTIL_TABLE_VACANCY_ON) {
+            ds_put_format(string, " vacancy:%"PRIu8""
+                          ",%"PRIu8"", pm.table_vacancy.vacancy_down,
+                          pm.table_vacancy.vacancy_up);
+        }
+    }
+}
+
+/* This function will print the Table description properties. */
+static void
+ofp_print_table_desc(struct ds *string, const struct ofputil_table_desc *td)
+{
+    ds_put_format(string, "\n  table %"PRIu8, td->table_id);
+    ds_put_cstr(string, ":\n");
+    ds_put_format(string, "   eviction=%s eviction_flags=",
+                  ofputil_table_eviction_to_string(td->eviction));
+    ofputil_put_eviction_flags(string, td->eviction_flags);
+    ds_put_char(string, '\n');
 }
 
 static void
@@ -1176,21 +1206,9 @@ ofp_print_meter_config(struct ds *s, const struct ofputil_meter_config *mc)
 }
 
 static void
-ofp_print_meter_mod(struct ds *s, const struct ofp_header *oh)
+ofp_print_meter_mod__(struct ds *s, const struct ofputil_meter_mod *mm)
 {
-    struct ofputil_meter_mod mm;
-    struct ofpbuf bands;
-    enum ofperr error;
-
-    ofpbuf_init(&bands, 64);
-    error = ofputil_decode_meter_mod(oh, &mm, &bands);
-    if (error) {
-        ofpbuf_uninit(&bands);
-        ofp_print_error(s, error);
-        return;
-    }
-
-    switch (mm.command) {
+    switch (mm->command) {
     case OFPMC13_ADD:
         ds_put_cstr(s, " ADD ");
         break;
@@ -1201,10 +1219,26 @@ ofp_print_meter_mod(struct ds *s, const struct ofp_header *oh)
         ds_put_cstr(s, " DEL ");
         break;
     default:
-        ds_put_format(s, " cmd:%d ", mm.command);
+        ds_put_format(s, " cmd:%d ", mm->command);
     }
 
-    ofp_print_meter_config(s, &mm.meter);
+    ofp_print_meter_config(s, &mm->meter);
+}
+
+static void
+ofp_print_meter_mod(struct ds *s, const struct ofp_header *oh)
+{
+    struct ofputil_meter_mod mm;
+    struct ofpbuf bands;
+    enum ofperr error;
+
+    ofpbuf_init(&bands, 64);
+    error = ofputil_decode_meter_mod(oh, &mm, &bands);
+    if (error) {
+        ofp_print_error(s, error);
+    } else {
+        ofp_print_meter_mod__(s, &mm);
+    }
     ofpbuf_uninit(&bands);
 }
 
@@ -1609,7 +1643,9 @@ ofp_print_table_stats_reply(struct ds *string, const struct ofp_header *oh)
     ofpbuf_use_const(&b, oh, ntohs(oh->length));
     ofpraw_pull_assert(&b);
 
-    for (;;) {
+    struct ofputil_table_features prev_features;
+    struct ofputil_table_stats prev_stats;
+    for (int i = 0;; i++) {
         struct ofputil_table_features features;
         struct ofputil_table_stats stats;
         int retval;
@@ -1622,7 +1658,12 @@ ofp_print_table_stats_reply(struct ds *string, const struct ofp_header *oh)
             return;
         }
 
-        ofp_print_table_features(string, &features, &stats);
+        ds_put_char(string, '\n');
+        ofp_print_table_features(string,
+                                 &features, i ? &prev_features : NULL,
+                                 &stats, i ? &prev_stats : NULL);
+        prev_features = features;
+        prev_stats = stats;
     }
 }
 
@@ -1891,64 +1932,220 @@ ofp_port_reason_to_string(enum ofp_port_reason reason,
     }
 }
 
+/* Returns a string form of 'reason'.  The return value is either a statically
+ * allocated constant string or the 'bufsize'-byte buffer 'reasonbuf'.
+ * 'bufsize' should be at least OFP_ASYNC_CONFIG_REASON_BUFSIZE. */
+static const char*
+ofp_role_reason_to_string(enum ofp14_controller_role_reason reason,
+                          char *reasonbuf, size_t bufsize)
+{
+    switch (reason) {
+    case OFPCRR_MASTER_REQUEST:
+        return "master_request";
+
+    case OFPCRR_CONFIG:
+        return "configuration_changed";
+
+    case OFPCRR_EXPERIMENTER:
+        return "experimenter_data_changed";
+
+    default:
+        snprintf(reasonbuf, bufsize, "%d", (int) reason);
+        return reasonbuf;
+    }
+}
+
+/* Returns a string form of 'reason'.  The return value is either a statically
+ * allocated constant string or the 'bufsize'-byte buffer 'reasonbuf'.
+ * 'bufsize' should be at least OFP_ASYNC_CONFIG_REASON_BUFSIZE. */
+static const char*
+ofp_table_reason_to_string(enum ofp14_table_reason reason,
+                           char *reasonbuf, size_t bufsize)
+{
+    switch (reason) {
+    case OFPTR_VACANCY_DOWN:
+        return "vacancy_down";
+
+    case OFPTR_VACANCY_UP:
+        return "vacancy_up";
+
+    default:
+        snprintf(reasonbuf, bufsize, "%d", (int) reason);
+        return reasonbuf;
+    }
+}
+
+/* Returns a string form of 'reason'.  The return value is either a statically
+ * allocated constant string or the 'bufsize'-byte buffer 'reasonbuf'.
+ * 'bufsize' should be at least OFP_ASYNC_CONFIG_REASON_BUFSIZE. */
+static const char*
+ofp_requestforward_reason_to_string(enum ofp14_requestforward_reason reason,
+                                    char *reasonbuf, size_t bufsize)
+{
+    switch (reason) {
+    case OFPRFR_GROUP_MOD:
+        return "group_mod_request";
+
+    case OFPRFR_METER_MOD:
+        return "meter_mod_request";
+
+    default:
+        snprintf(reasonbuf, bufsize, "%d", (int) reason);
+        return reasonbuf;
+    }
+}
+
+static const char *
+ofp_async_config_reason_to_string(uint32_t reason,
+                                  enum ofputil_async_msg_type type,
+                                  char *reasonbuf, size_t bufsize)
+{
+    switch (type) {
+    case OAM_PACKET_IN:
+        return ofputil_packet_in_reason_to_string(reason, reasonbuf, bufsize);
+
+    case OAM_PORT_STATUS:
+        return ofp_port_reason_to_string(reason, reasonbuf, bufsize);
+
+    case OAM_FLOW_REMOVED:
+        return ofp_flow_removed_reason_to_string(reason, reasonbuf, bufsize);
+
+    case OAM_ROLE_STATUS:
+        return ofp_role_reason_to_string(reason, reasonbuf, bufsize);
+
+    case OAM_TABLE_STATUS:
+        return ofp_table_reason_to_string(reason, reasonbuf, bufsize);
+
+    case OAM_REQUESTFORWARD:
+        return ofp_requestforward_reason_to_string(reason, reasonbuf, bufsize);
+
+    case OAM_N_TYPES:
+    default:
+        return "Unknown asynchronous configuration message type";
+    }
+}
+
+
+#define OFP_ASYNC_CONFIG_REASON_BUFSIZE (INT_STRLEN(int) + 1)
 static void
 ofp_print_nxt_set_async_config(struct ds *string,
-                               const struct nx_async_config *nac)
+                               const struct ofp_header *oh)
 {
-    int i;
+    int i, j;
+    enum ofpraw raw;
 
-    for (i = 0; i < 2; i++) {
-        int j;
+    ofpraw_decode(&raw, oh);
 
-        ds_put_format(string, "\n %s:\n", i == 0 ? "master" : "slave");
+    if (raw == OFPRAW_OFPT13_SET_ASYNC ||
+        raw == OFPRAW_NXT_SET_ASYNC_CONFIG ||
+        raw == OFPRAW_OFPT13_GET_ASYNC_REPLY) {
+        const struct nx_async_config *nac = ofpmsg_body(oh);
 
-        ds_put_cstr(string, "       PACKET_IN:");
-        for (j = 0; j < 32; j++) {
-            if (nac->packet_in_mask[i] & htonl(1u << j)) {
-                char reasonbuf[OFPUTIL_PACKET_IN_REASON_BUFSIZE];
-                const char *reason;
+        for (i = 0; i < 2; i++) {
 
-                reason = ofputil_packet_in_reason_to_string(j, reasonbuf,
+            ds_put_format(string, "\n %s:\n", i == 0 ? "master" : "slave");
+
+            ds_put_cstr(string, "       PACKET_IN:");
+            for (j = 0; j < 32; j++) {
+                if (nac->packet_in_mask[i] & htonl(1u << j)) {
+                    char reasonbuf[OFPUTIL_PACKET_IN_REASON_BUFSIZE];
+                    const char *reason;
+
+                    reason = ofputil_packet_in_reason_to_string(j, reasonbuf,
                                                             sizeof reasonbuf);
-                ds_put_format(string, " %s", reason);
+                    ds_put_format(string, " %s", reason);
+                }
             }
-        }
-        if (!nac->packet_in_mask[i]) {
-            ds_put_cstr(string, " (off)");
-        }
-        ds_put_char(string, '\n');
+            if (!nac->packet_in_mask[i]) {
+                ds_put_cstr(string, " (off)");
+            }
+            ds_put_char(string, '\n');
 
-        ds_put_cstr(string, "     PORT_STATUS:");
-        for (j = 0; j < 32; j++) {
-            if (nac->port_status_mask[i] & htonl(1u << j)) {
-                char reasonbuf[OFP_PORT_REASON_BUFSIZE];
-                const char *reason;
+            ds_put_cstr(string, "     PORT_STATUS:");
+            for (j = 0; j < 32; j++) {
+                if (nac->port_status_mask[i] & htonl(1u << j)) {
+                    char reasonbuf[OFP_PORT_REASON_BUFSIZE];
+                    const char *reason;
 
-                reason = ofp_port_reason_to_string(j, reasonbuf,
-                                                   sizeof reasonbuf);
-                ds_put_format(string, " %s", reason);
+                    reason = ofp_port_reason_to_string(j, reasonbuf,
+                                                       sizeof reasonbuf);
+                    ds_put_format(string, " %s", reason);
+                }
             }
-        }
-        if (!nac->port_status_mask[i]) {
-            ds_put_cstr(string, " (off)");
-        }
-        ds_put_char(string, '\n');
+            if (!nac->port_status_mask[i]) {
+                ds_put_cstr(string, " (off)");
+            }
+            ds_put_char(string, '\n');
 
-        ds_put_cstr(string, "    FLOW_REMOVED:");
-        for (j = 0; j < 32; j++) {
-            if (nac->flow_removed_mask[i] & htonl(1u << j)) {
-                char reasonbuf[OFP_FLOW_REMOVED_REASON_BUFSIZE];
-                const char *reason;
+            ds_put_cstr(string, "    FLOW_REMOVED:");
+            for (j = 0; j < 32; j++) {
+                if (nac->flow_removed_mask[i] & htonl(1u << j)) {
+                    char reasonbuf[OFP_FLOW_REMOVED_REASON_BUFSIZE];
+                    const char *reason;
 
-                reason = ofp_flow_removed_reason_to_string(j, reasonbuf,
+                    reason = ofp_flow_removed_reason_to_string(j, reasonbuf,
                                                            sizeof reasonbuf);
-                ds_put_format(string, " %s", reason);
+                    ds_put_format(string, " %s", reason);
+                }
+            }
+            if (!nac->flow_removed_mask[i]) {
+                ds_put_cstr(string, " (off)");
             }
+            ds_put_char(string, '\n');
         }
-        if (!nac->flow_removed_mask[i]) {
-            ds_put_cstr(string, " (off)");
+    } else if (raw == OFPRAW_OFPT14_SET_ASYNC ||
+               raw == OFPRAW_OFPT14_GET_ASYNC_REPLY) {
+        uint32_t role[2][OAM_N_TYPES] = {{0}};
+        uint32_t type;
+
+        ofputil_decode_set_async_config(oh, role[0], role[1], true);
+        for (i = 0; i < 2; i++) {
+
+            ds_put_format(string, "\n %s:\n", i == 0 ? "master" : "slave");
+            for (type = 0; type < OAM_N_TYPES; type++) {
+                switch (type) {
+                case OAM_PACKET_IN:
+                    ds_put_cstr(string, "       PACKET_IN:");
+                    break;
+
+                case OAM_PORT_STATUS:
+                    ds_put_cstr(string, "     PORT_STATUS:");
+                    break;
+
+                case OAM_FLOW_REMOVED:
+                    ds_put_cstr(string, "    FLOW_REMOVED:");
+                    break;
+
+                case OAM_ROLE_STATUS:
+                    ds_put_cstr(string, "     ROLE_STATUS:");
+                    break;
+
+                case OAM_TABLE_STATUS:
+                    ds_put_cstr(string, "    TABLE_STATUS:");
+                    break;
+
+                case OAM_REQUESTFORWARD:
+                    ds_put_cstr(string, "  REQUESTFORWARD:");
+                    break;
+                }
+
+                for (j = 0; j < 32; j++) {
+                    if (role[i][type] & (1u << j)) {
+                        char reasonbuf[OFP_ASYNC_CONFIG_REASON_BUFSIZE];
+                        const char *reason;
+
+                        reason = ofp_async_config_reason_to_string(j, type,
+                                                                   reasonbuf,
+                                                           sizeof reasonbuf);
+                        ds_put_format(string, " %s", reason);
+                    }
+                }
+                if (!role[i][type]) {
+                    ds_put_cstr(string, " (off)");
+                }
+                ds_put_char(string, '\n');
+            }
         }
-        ds_put_char(string, '\n');
     }
 }
 
@@ -2161,7 +2358,8 @@ ofp_print_bucket_id(struct ds *s, const char *label, uint32_t bucket_id,
 
 static void
 ofp_print_group(struct ds *s, uint32_t group_id, uint8_t type,
-                struct ovs_list *p_buckets, struct ofputil_group_props *props,
+                const struct ovs_list *p_buckets,
+                const struct ofputil_group_props *props,
                 enum ofp_version ofp_version, bool suppress_type)
 {
     struct ofputil_bucket *bucket;
@@ -2175,22 +2373,20 @@ ofp_print_group(struct ds *s, uint32_t group_id, uint8_t type,
     }
 
     if (props->selection_method[0]) {
-        size_t mark, start;
-
-        ds_put_format(s, ",selection_method=%s,", props->selection_method);
+        ds_put_format(s, ",selection_method=%s", props->selection_method);
         if (props->selection_method_param) {
-            ds_put_format(s, "selection_method_param=%"PRIu64",",
+            ds_put_format(s, ",selection_method_param=%"PRIu64,
                           props->selection_method_param);
         }
 
-        /* Allow rewinding to immediately before the trailing ',' */
-        mark = s->length - 1;
-
-        ds_put_cstr(s, "fields=");
-        start = s->length;
-        oxm_format_field_array(s, &props->fields);
-        if (s->length == start) {
-            ds_truncate(s, mark);
+        size_t n = bitmap_count1(props->fields.used.bm, MFF_N_IDS);
+        if (n == 1) {
+            ds_put_cstr(s, ",fields=");
+            oxm_format_field_array(s, &props->fields);
+        } else if (n > 1) {
+            ds_put_cstr(s, ",fields(");
+            oxm_format_field_array(s, &props->fields);
+            ds_put_char(s, ')');
         }
     }
 
@@ -2204,13 +2400,13 @@ ofp_print_group(struct ds *s, uint32_t group_id, uint8_t type,
         ds_put_cstr(s, "bucket=");
 
         ofp_print_bucket_id(s, "bucket_id:", bucket->bucket_id, ofp_version);
-        if (bucket->weight != 1) {
+        if (bucket->weight != (type == OFPGT11_SELECT ? 1 : 0)) {
             ds_put_format(s, "weight:%"PRIu16",", bucket->weight);
         }
         if (bucket->watch_port != OFPP_NONE) {
             ds_put_format(s, "watch_port:%"PRIu32",", bucket->watch_port);
         }
-        if (bucket->watch_group != OFPG11_ANY) {
+        if (bucket->watch_group != OFPG_ANY) {
             ds_put_format(s, "watch_group:%"PRIu32",", bucket->watch_group);
         }
 
@@ -2357,22 +2553,15 @@ ofp_print_group_features(struct ds *string, const struct ofp_header *oh)
 }
 
 static void
-ofp_print_group_mod(struct ds *s, const struct ofp_header *oh)
+ofp_print_group_mod__(struct ds *s, enum ofp_version ofp_version,
+                      const struct ofputil_group_mod *gm)
 {
-    struct ofputil_group_mod gm;
-    int error;
     bool bucket_command = false;
 
-    error = ofputil_decode_group_mod(oh, &gm);
-    if (error) {
-        ofp_print_error(s, error);
-        return;
-    }
-
     ds_put_char(s, '\n');
 
     ds_put_char(s, ' ');
-    switch (gm.command) {
+    switch (gm->command) {
     case OFPGC11_ADD:
         ds_put_cstr(s, "ADD");
         break;
@@ -2396,17 +2585,31 @@ ofp_print_group_mod(struct ds *s, const struct ofp_header *oh)
         break;
 
     default:
-        ds_put_format(s, "cmd:%"PRIu16"", gm.command);
+        ds_put_format(s, "cmd:%"PRIu16"", gm->command);
     }
     ds_put_char(s, ' ');
 
     if (bucket_command) {
         ofp_print_bucket_id(s, "command_bucket_id:",
-                            gm.command_bucket_id, oh->version);
+                            gm->command_bucket_id, ofp_version);
     }
 
-    ofp_print_group(s, gm.group_id, gm.type, &gm.buckets, &gm.props,
-                    oh->version, bucket_command);
+    ofp_print_group(s, gm->group_id, gm->type, &gm->buckets, &gm->props,
+                    ofp_version, bucket_command);
+}
+
+static void
+ofp_print_group_mod(struct ds *s, const struct ofp_header *oh)
+{
+    struct ofputil_group_mod gm;
+    int error;
+
+    error = ofputil_decode_group_mod(oh, &gm);
+    if (error) {
+        ofp_print_error(s, error);
+        return;
+    }
+    ofp_print_group_mod__(s, oh->version, &gm);
     ofputil_bucket_list_destroy(&gm.buckets);
 }
 
@@ -2447,7 +2650,9 @@ table_action_features_empty(const struct ofputil_table_action_features *taf)
 
 static void
 print_table_instruction_features(
-    struct ds *s, const struct ofputil_table_instruction_features *tif)
+    struct ds *s,
+    const struct ofputil_table_instruction_features *tif,
+    const struct ofputil_table_instruction_features *prev_tif)
 {
     int start, end;
 
@@ -2470,19 +2675,28 @@ print_table_instruction_features(
     }
 
     if (tif->instructions) {
-        ds_put_cstr(s, "      instructions: ");
-        int i;
+        if (prev_tif && tif->instructions == prev_tif->instructions) {
+            ds_put_cstr(s, "      (same instructions)\n");
+        } else {
+            ds_put_cstr(s, "      instructions: ");
+            int i;
 
-        for (i = 0; i < 32; i++) {
-            if (tif->instructions & (1u << i)) {
-                ds_put_format(s, "%s,", ovs_instruction_name_from_type(i));
+            for (i = 0; i < 32; i++) {
+                if (tif->instructions & (1u << i)) {
+                    ds_put_format(s, "%s,", ovs_instruction_name_from_type(i));
+                }
             }
+            ds_chomp(s, ',');
+            ds_put_char(s, '\n');
         }
-        ds_chomp(s, ',');
-        ds_put_char(s, '\n');
     }
 
-    if (!table_action_features_equal(&tif->write, &tif->apply)) {
+    if (prev_tif
+        && table_action_features_equal(&tif->write, &prev_tif->write)
+        && table_action_features_equal(&tif->apply, &prev_tif->apply)
+        && !bitmap_is_all_zeros(tif->write.set_fields.bm, MFF_N_IDS)) {
+        ds_put_cstr(s, "      (same actions)\n");
+    } else if (!table_action_features_equal(&tif->write, &tif->apply)) {
         ds_put_cstr(s, "      Write-Actions features:\n");
         print_table_action_features(s, &tif->write);
         ds_put_cstr(s, "      Apply-Actions features:\n");
@@ -2515,24 +2729,79 @@ table_instruction_features_empty(
             && table_action_features_empty(&tif->apply));
 }
 
-static void
+static bool
+table_features_equal(const struct ofputil_table_features *a,
+                     const struct ofputil_table_features *b)
+{
+    return (a->metadata_match == b->metadata_match
+            && a->metadata_write == b->metadata_write
+            && a->miss_config == b->miss_config
+            && a->supports_eviction == b->supports_eviction
+            && a->supports_vacancy_events == b->supports_vacancy_events
+            && a->max_entries == b->max_entries
+            && table_instruction_features_equal(&a->nonmiss, &b->nonmiss)
+            && table_instruction_features_equal(&a->miss, &b->miss)
+            && bitmap_equal(a->match.bm, b->match.bm, MFF_N_IDS));
+}
+
+static bool
+table_features_empty(const struct ofputil_table_features *tf)
+{
+    return (!tf->metadata_match
+            && !tf->metadata_write
+            && tf->miss_config == OFPUTIL_TABLE_MISS_DEFAULT
+            && tf->supports_eviction < 0
+            && tf->supports_vacancy_events < 0
+            && !tf->max_entries
+            && table_instruction_features_empty(&tf->nonmiss)
+            && table_instruction_features_empty(&tf->miss)
+            && bitmap_is_all_zeros(tf->match.bm, MFF_N_IDS));
+}
+
+static bool
+table_stats_equal(const struct ofputil_table_stats *a,
+                  const struct ofputil_table_stats *b)
+{
+    return (a->active_count == b->active_count
+            && a->lookup_count == b->lookup_count
+            && a->matched_count == b->matched_count);
+}
+
+void
 ofp_print_table_features(struct ds *s,
                          const struct ofputil_table_features *features,
-                         const struct ofputil_table_stats *stats)
+                         const struct ofputil_table_features *prev_features,
+                         const struct ofputil_table_stats *stats,
+                         const struct ofputil_table_stats *prev_stats)
 {
     int i;
 
-    ds_put_format(s, "\n  table %"PRIu8, features->table_id);
+    ds_put_format(s, "  table %"PRIu8, features->table_id);
     if (features->name[0]) {
         ds_put_format(s, " (\"%s\")", features->name);
     }
-    ds_put_cstr(s, ":\n");
+    ds_put_char(s, ':');
+
+    bool same_stats = prev_stats && table_stats_equal(stats, prev_stats);
+    bool same_features = prev_features && table_features_equal(features,
+                                                               prev_features);
+    if ((!stats || same_stats) && same_features) {
+        ds_put_cstr(s, " ditto");
+        return;
+    }
+    ds_put_char(s, '\n');
     if (stats) {
         ds_put_format(s, "    active=%"PRIu32", ", stats->active_count);
         ds_put_format(s, "lookup=%"PRIu64", ", stats->lookup_count);
         ds_put_format(s, "matched=%"PRIu64"\n", stats->matched_count);
     }
-    if (features->metadata_match || features->metadata_match) {
+    if (same_features) {
+        if (!table_features_empty(features)) {
+            ds_put_cstr(s, "    (same features)\n");
+        }
+        return;
+    }
+    if (features->metadata_match || features->metadata_write) {
         ds_put_format(s, "    metadata: match=%#"PRIx64" write=%#"PRIx64"\n",
                       ntohll(features->metadata_match),
                       ntohll(features->metadata_write));
@@ -2558,29 +2827,45 @@ ofp_print_table_features(struct ds *s,
         ds_put_format(s, "    max_entries=%"PRIu32"\n", features->max_entries);
     }
 
-    if (!table_instruction_features_equal(&features->nonmiss,
-                                          &features->miss)) {
+    const struct ofputil_table_instruction_features *prev_nonmiss
+        = prev_features ? &prev_features->nonmiss : NULL;
+    const struct ofputil_table_instruction_features *prev_miss
+        = prev_features ? &prev_features->miss : NULL;
+    if (prev_features
+        && table_instruction_features_equal(&features->nonmiss, prev_nonmiss)
+        && table_instruction_features_equal(&features->miss, prev_miss)) {
+        if (!table_instruction_features_empty(&features->nonmiss)) {
+            ds_put_cstr(s, "    (same instructions)\n");
+        }
+    } else if (!table_instruction_features_equal(&features->nonmiss,
+                                                 &features->miss)) {
         ds_put_cstr(s, "    instructions (other than table miss):\n");
-        print_table_instruction_features(s, &features->nonmiss);
+        print_table_instruction_features(s, &features->nonmiss, prev_nonmiss);
         ds_put_cstr(s, "    instructions (table miss):\n");
-        print_table_instruction_features(s, &features->miss);
+        print_table_instruction_features(s, &features->miss, prev_miss);
     } else if (!table_instruction_features_empty(&features->nonmiss)) {
         ds_put_cstr(s, "    instructions (table miss and others):\n");
-        print_table_instruction_features(s, &features->nonmiss);
+        print_table_instruction_features(s, &features->nonmiss, prev_nonmiss);
     }
 
-    if (!bitmap_is_all_zeros(features->match.bm, MFF_N_IDS)){
-        ds_put_cstr(s, "    matching:\n");
-        BITMAP_FOR_EACH_1 (i, MFF_N_IDS, features->match.bm) {
-            const struct mf_field *f = mf_from_id(i);
-            bool mask = bitmap_is_set(features->mask.bm, i);
-            bool wildcard = bitmap_is_set(features->wildcard.bm, i);
-
-            ds_put_format(s, "      %s: %s\n",
-                          f->name,
-                          (mask ? "arbitrary mask"
-                           : wildcard ? "exact match or wildcard"
-                           : "must exact match"));
+    if (!bitmap_is_all_zeros(features->match.bm, MFF_N_IDS)) {
+        if (prev_features
+            && bitmap_equal(features->match.bm, prev_features->match.bm,
+                            MFF_N_IDS)) {
+            ds_put_cstr(s, "    (same matching)\n");
+        } else {
+            ds_put_cstr(s, "    matching:\n");
+            BITMAP_FOR_EACH_1 (i, MFF_N_IDS, features->match.bm) {
+                const struct mf_field *f = mf_from_id(i);
+                bool mask = bitmap_is_set(features->mask.bm, i);
+                bool wildcard = bitmap_is_set(features->wildcard.bm, i);
+
+                ds_put_format(s, "      %s: %s\n",
+                              f->name,
+                              (mask ? "arbitrary mask"
+                               : wildcard ? "exact match or wildcard"
+                               : "must exact match"));
+            }
         }
     }
 }
@@ -2592,7 +2877,8 @@ ofp_print_table_features_reply(struct ds *s, const struct ofp_header *oh)
 
     ofpbuf_use_const(&b, oh, ntohs(oh->length));
 
-    for (;;) {
+    struct ofputil_table_features prev;
+    for (int i = 0; ; i++) {
         struct ofputil_table_features tf;
         int retval;
 
@@ -2603,7 +2889,32 @@ ofp_print_table_features_reply(struct ds *s, const struct ofp_header *oh)
             }
             return;
         }
-        ofp_print_table_features(s, &tf, NULL);
+
+        ds_put_char(s, '\n');
+        ofp_print_table_features(s, &tf, i ? &prev : NULL, NULL, NULL);
+        prev = tf;
+    }
+}
+
+static void
+ofp_print_table_desc_reply(struct ds *s, const struct ofp_header *oh)
+{
+    struct ofpbuf b;
+
+    ofpbuf_use_const(&b, oh, ntohs(oh->length));
+
+    for (;;) {
+        struct ofputil_table_desc td;
+        int retval;
+
+        retval = ofputil_decode_table_desc(&b, &td, oh->version);
+        if (retval) {
+            if (retval != EOF) {
+                ofp_print_error(s, retval);
+            }
+            return;
+        }
+        ofp_print_table_desc(s, &td);
     }
 }
 
@@ -2770,6 +3081,36 @@ ofp_print_geneve_table_reply(struct ds *s, const struct ofp_header *oh)
     ofputil_uninit_geneve_table(&gtr.mappings);
 }
 
+/* This function will print the request forward message. The reason for
+ * request forward is taken from rf.request.type */
+static void
+ofp_print_requestforward(struct ds *string, const struct ofp_header *oh)
+{
+    struct ofputil_requestforward rf;
+    enum ofperr error;
+
+    error = ofputil_decode_requestforward(oh, &rf);
+    if (error) {
+        ofp_print_error(string, error);
+        return;
+    }
+
+    ds_put_cstr(string, " reason=");
+
+    switch (rf.reason) {
+    case OFPRFR_GROUP_MOD:
+        ds_put_cstr(string, "group_mod");
+        ofp_print_group_mod__(string, oh->version, rf.group_mod);
+        break;
+
+    case OFPRFR_METER_MOD:
+        ds_put_cstr(string, "meter_mod");
+        ofp_print_meter_mod__(string, rf.meter_mod);
+        break;
+    }
+    ofputil_destroy_requestforward(&rf);
+}
+
 static void
 ofp_to_string__(const struct ofp_header *oh, enum ofpraw raw,
                 struct ds *string, int verbosity)
@@ -2814,6 +3155,11 @@ ofp_to_string__(const struct ofp_header *oh, enum ofpraw raw,
         ofp_print_table_features_reply(string, oh);
         break;
 
+    case OFPTYPE_TABLE_DESC_REQUEST:
+    case OFPTYPE_TABLE_DESC_REPLY:
+        ofp_print_table_desc_reply(string, oh);
+        break;
+
     case OFPTYPE_HELLO:
         ofp_print_hello(string, oh);
         break;
@@ -2894,6 +3240,10 @@ ofp_to_string__(const struct ofp_header *oh, enum ofpraw raw,
         ofp_print_role_status_message(string, oh);
         break;
 
+    case OFPTYPE_REQUESTFORWARD:
+        ofp_print_requestforward(string, oh);
+        break;
+
     case OFPTYPE_METER_STATS_REQUEST:
     case OFPTYPE_METER_CONFIG_STATS_REQUEST:
         ofp_print_stats(string, oh);
@@ -3001,7 +3351,7 @@ ofp_to_string__(const struct ofp_header *oh, enum ofpraw raw,
 
     case OFPTYPE_GET_ASYNC_REPLY:
     case OFPTYPE_SET_ASYNC_CONFIG:
-        ofp_print_nxt_set_async_config(string, ofpmsg_body(oh));
+        ofp_print_nxt_set_async_config(string, oh);
         break;
     case OFPTYPE_GET_ASYNC_REQUEST:
         break;