ofp-print: Enable printing OF1.4 version
[sliver-openvswitch.git] / lib / ofp-print.c
index 02d4380..38e228c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013 Nicira, Inc.
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -46,6 +46,7 @@
 #include "packets.h"
 #include "type-props.h"
 #include "unaligned.h"
+#include "odp-util.h"
 #include "util.h"
 
 static void ofp_print_queue_name(struct ds *string, uint32_t port);
@@ -58,23 +59,27 @@ char *
 ofp_packet_to_string(const void *data, size_t len)
 {
     struct ds ds = DS_EMPTY_INITIALIZER;
+    const struct pkt_metadata md = PKT_METADATA_INITIALIZER(0);
     struct ofpbuf buf;
     struct flow flow;
+    size_t l4_size;
 
     ofpbuf_use_const(&buf, data, len);
-    flow_extract(&buf, 0, 0, NULL, NULL, &flow);
+    flow_extract(&buf, &md, &flow);
     flow_format(&ds, &flow);
 
-    if (buf.l7) {
-        if (flow.nw_proto == IPPROTO_TCP) {
-            struct tcp_header *th = buf.l4;
-            ds_put_format(&ds, " tcp_csum:%"PRIx16,
-                          ntohs(th->tcp_csum));
-        } else if (flow.nw_proto == IPPROTO_UDP) {
-            struct udp_header *uh = buf.l4;
-            ds_put_format(&ds, " udp_csum:%"PRIx16,
-                          ntohs(uh->udp_csum));
-        }
+    l4_size = ofpbuf_l4_size(&buf);
+
+    if (flow.nw_proto == IPPROTO_TCP && l4_size >= TCP_HEADER_LEN) {
+        struct tcp_header *th = ofpbuf_l4(&buf);
+        ds_put_format(&ds, " tcp_csum:%"PRIx16, ntohs(th->tcp_csum));
+    } else if (flow.nw_proto == IPPROTO_UDP && l4_size >= UDP_HEADER_LEN) {
+        struct udp_header *uh = ofpbuf_l4(&buf);
+        ds_put_format(&ds, " udp_csum:%"PRIx16, ntohs(uh->udp_csum));
+    } else if (flow.nw_proto == IPPROTO_SCTP && l4_size >= SCTP_HEADER_LEN) {
+        struct sctp_header *sh = ofpbuf_l4(&buf);
+        ds_put_format(&ds, " sctp_csum:%"PRIx32,
+                      ntohl(get_16aligned_be32(&sh->sctp_csum)));
     }
 
     ds_put_char(&ds, '\n');
@@ -101,11 +106,11 @@ ofp_print_packet_in(struct ds *string, const struct ofp_header *oh,
         ds_put_format(string, " table_id=%"PRIu8, pin.table_id);
     }
 
-    if (pin.cookie) {
+    if (pin.cookie != OVS_BE64_MAX) {
         ds_put_format(string, " cookie=0x%"PRIx64, ntohll(pin.cookie));
     }
 
-    ds_put_format(string, " total_len=%"PRIu16" in_port=", pin.total_len);
+    ds_put_format(string, " total_len=%"PRIuSIZE" in_port=", pin.total_len);
     ofputil_format_port(pin.fmd.in_port, string);
 
     if (pin.fmd.tun_id != htonll(0)) {
@@ -138,7 +143,7 @@ ofp_print_packet_in(struct ds *string, const struct ofp_header *oh,
                   ofputil_packet_in_reason_to_string(pin.reason, reasonbuf,
                                                      sizeof reasonbuf));
 
-    ds_put_format(string, " data_len=%zu", pin.packet_len);
+    ds_put_format(string, " data_len=%"PRIuSIZE, pin.packet_len);
     if (pin.buffer_id == UINT32_MAX) {
         ds_put_format(string, " (unbuffered)");
         if (pin.total_len != pin.packet_len) {
@@ -181,11 +186,11 @@ ofp_print_packet_out(struct ds *string, const struct ofp_header *oh,
     ds_put_cstr(string, " in_port=");
     ofputil_format_port(po.in_port, string);
 
-    ds_put_char(string, ' ');
+    ds_put_cstr(string, " actions=");
     ofpacts_format(po.ofpacts, po.ofpacts_len, string);
 
     if (po.buffer_id == UINT32_MAX) {
-        ds_put_format(string, " data_len=%zu", po.packet_len);
+        ds_put_format(string, " data_len=%"PRIuSIZE, po.packet_len);
         if (verbosity > 0 && po.packet_len > 0) {
             char *packet = ofp_packet_to_string(po.packet, po.packet_len);
             ds_put_char(string, '\n');
@@ -534,9 +539,10 @@ ofp_print_switch_features(struct ds *string, const struct ofp_header *oh)
     case OFP12_VERSION:
         break;
     case OFP13_VERSION:
+    case OFP14_VERSION:
         return; /* no ports in ofp13_switch_features */
     default:
-        NOT_REACHED();
+        OVS_NOT_REACHED();
     }
 
     ofp_print_phy_ports(string, oh->version, &b);
@@ -566,7 +572,7 @@ ofp_print_switch_config(struct ds *string, const struct ofp_switch_config *osc)
 
 static void print_wild(struct ds *string, const char *leader, int is_wild,
             int verbosity, const char *format, ...)
-            __attribute__((format(printf, 5, 6)));
+            PRINTF_FORMAT(5, 6);
 
 static void print_wild(struct ds *string, const char *leader, int is_wild,
                        int verbosity, const char *format, ...)
@@ -649,6 +655,8 @@ ofp10_match_to_string(const struct ofp10_match *om, int verbosity)
                     ds_put_cstr(&f, "tcp,");
                 } else if (om->nw_proto == IPPROTO_UDP) {
                     ds_put_cstr(&f, "udp,");
+                } else if (om->nw_proto == IPPROTO_SCTP) {
+                    ds_put_cstr(&f, "sctp,");
                 } else {
                     ds_put_cstr(&f, "ip,");
                     skip_proto = false;
@@ -718,29 +726,28 @@ ofp10_match_to_string(const struct ofp10_match *om, int verbosity)
 }
 
 static void
-ofp_print_flow_flags(struct ds *s, uint16_t flags)
+ofp_print_flow_flags(struct ds *s, enum ofputil_flow_mod_flags flags)
 {
-    if (flags & OFPFF_SEND_FLOW_REM) {
+    if (flags & OFPUTIL_FF_SEND_FLOW_REM) {
         ds_put_cstr(s, "send_flow_rem ");
     }
-    if (flags & OFPFF_CHECK_OVERLAP) {
+    if (flags & OFPUTIL_FF_CHECK_OVERLAP) {
         ds_put_cstr(s, "check_overlap ");
     }
-    if (flags & OFPFF12_RESET_COUNTS) {
+    if (flags & OFPUTIL_FF_RESET_COUNTS) {
         ds_put_cstr(s, "reset_counts ");
     }
-    if (flags & OFPFF13_NO_PKT_COUNTS) {
+    if (flags & OFPUTIL_FF_NO_PKT_COUNTS) {
         ds_put_cstr(s, "no_packet_counts ");
     }
-    if (flags & OFPFF13_NO_BYT_COUNTS) {
+    if (flags & OFPUTIL_FF_NO_BYT_COUNTS) {
         ds_put_cstr(s, "no_byte_counts ");
     }
-
-    flags &= ~(OFPFF_SEND_FLOW_REM | OFPFF_CHECK_OVERLAP
-               | OFPFF12_RESET_COUNTS
-               | OFPFF13_NO_PKT_COUNTS | OFPFF13_NO_BYT_COUNTS);
-    if (flags) {
-        ds_put_format(s, "flags:0x%"PRIx16" ", flags);
+    if (flags & OFPUTIL_FF_HIDDEN_FIELDS) {
+        ds_put_cstr(s, "allow_hidden_fields ");
+    }
+    if (flags & OFPUTIL_FF_NO_READONLY) {
+        ds_put_cstr(s, "no_readonly_table ");
     }
 }
 
@@ -758,7 +765,8 @@ ofp_print_flow_mod(struct ds *s, const struct ofp_header *oh, int verbosity)
     protocol = ofputil_protocol_set_tid(protocol, true);
 
     ofpbuf_init(&ofpacts, 64);
-    error = ofputil_decode_flow_mod(&fm, oh, protocol, &ofpacts);
+    error = ofputil_decode_flow_mod(&fm, oh, protocol, &ofpacts,
+                                    OFPP_MAX, 255);
     if (error) {
         ofpbuf_uninit(&ofpacts);
         ofp_print_error(s, error);
@@ -818,7 +826,7 @@ ofp_print_flow_mod(struct ds *s, const struct ofp_header *oh, int verbosity)
     if (ds_last(s) != ' ') {
         ds_put_char(s, ' ');
     }
-    if (fm.new_cookie != htonll(0) && fm.new_cookie != htonll(UINT64_MAX)) {
+    if (fm.new_cookie != htonll(0) && fm.new_cookie != OVS_BE64_MAX) {
         ds_put_format(s, "cookie:0x%"PRIx64" ", ntohll(fm.new_cookie));
     }
     if (fm.cookie_mask != htonll(0)) {
@@ -842,10 +850,16 @@ ofp_print_flow_mod(struct ds *s, const struct ofp_header *oh, int verbosity)
         ofputil_format_port(fm.out_port, s);
         ds_put_char(s, ' ');
     }
-    if (fm.flags != 0) {
-        ofp_print_flow_flags(s, fm.flags);
+
+    if (oh->version == OFP10_VERSION || oh->version == OFP11_VERSION) {
+        /* Don't print the reset_counts flag for OF1.0 and OF1.1 because those
+         * versions don't really have such a flag and printing one is likely to
+         * confuse people. */
+        fm.flags &= ~OFPUTIL_FF_RESET_COUNTS;
     }
+    ofp_print_flow_flags(s, fm.flags);
 
+    ds_put_cstr(s, "actions=");
     ofpacts_format(fm.ofpacts, fm.ofpacts_len, s);
     ofpbuf_uninit(&ofpacts);
 }
@@ -854,10 +868,26 @@ static void
 ofp_print_duration(struct ds *string, unsigned int sec, unsigned int nsec)
 {
     ds_put_format(string, "%u", sec);
+
+    /* If there are no fractional seconds, don't print any decimals.
+     *
+     * If the fractional seconds can be expressed exactly as milliseconds,
+     * print 3 decimals.  Open vSwitch provides millisecond precision for most
+     * time measurements, so printing 3 decimals every time makes it easier to
+     * spot real changes in flow dumps that refresh themselves quickly.
+     *
+     * If the fractional seconds are more precise than milliseconds, print the
+     * number of decimals needed to express them exactly.
+     */
     if (nsec > 0) {
-        ds_put_format(string, ".%09u", nsec);
-        while (string->string[string->length - 1] == '0') {
-            string->length--;
+        unsigned int msec = nsec / 1000000;
+        if (msec * 1000000 == nsec) {
+            ds_put_format(string, ".%03u", msec);
+        } else {
+            ds_put_format(string, ".%09u", nsec);
+            while (string->string[string->length - 1] == '0') {
+                string->length--;
+            }
         }
     }
     ds_put_char(string, 's');
@@ -960,6 +990,114 @@ ofp_print_port_mod(struct ds *string, const struct ofp_header *oh)
     }
 }
 
+static void
+ofp_print_table_miss_config(struct ds *string, const uint32_t config)
+{
+    uint32_t table_miss_config = config & OFPTC11_TABLE_MISS_MASK;
+
+    switch (table_miss_config) {
+    case OFPTC11_TABLE_MISS_CONTROLLER:
+        ds_put_cstr(string, "controller\n");
+        break;
+    case OFPTC11_TABLE_MISS_CONTINUE:
+        ds_put_cstr(string, "continue\n");
+        break;
+    case OFPTC11_TABLE_MISS_DROP:
+        ds_put_cstr(string, "drop\n");
+        break;
+    default:
+        ds_put_cstr(string, "Unknown\n");
+        break;
+    }
+}
+
+static void
+ofp_print_table_mod(struct ds *string, const struct ofp_header *oh)
+{
+    struct ofputil_table_mod pm;
+    enum ofperr error;
+
+    error = ofputil_decode_table_mod(oh, &pm);
+    if (error) {
+        ofp_print_error(string, error);
+        return;
+    }
+
+    if (pm.table_id == 0xff) {
+        ds_put_cstr(string, " table_id: ALL_TABLES");
+    } else {
+        ds_put_format(string, " table_id=%"PRIu8, pm.table_id);
+    }
+
+    ds_put_cstr(string, ", flow_miss_config=");
+    ofp_print_table_miss_config(string, pm.config);
+}
+
+static void
+ofp_print_queue_get_config_request(struct ds *string,
+                                   const struct ofp_header *oh)
+{
+    enum ofperr error;
+    ofp_port_t port;
+
+    error = ofputil_decode_queue_get_config_request(oh, &port);
+    if (error) {
+        ofp_print_error(string, error);
+        return;
+    }
+
+    ds_put_cstr(string, " port=");
+    ofputil_format_port(port, string);
+}
+
+static void
+print_queue_rate(struct ds *string, const char *name, unsigned int rate)
+{
+    if (rate <= 1000) {
+        ds_put_format(string, " %s:%u.%u%%", name, rate / 10, rate % 10);
+    } else if (rate < UINT16_MAX) {
+        ds_put_format(string, " %s:(disabled)", name);
+    }
+}
+
+static void
+ofp_print_queue_get_config_reply(struct ds *string,
+                                 const struct ofp_header *oh)
+{
+    enum ofperr error;
+    struct ofpbuf b;
+    ofp_port_t port;
+
+    ofpbuf_use_const(&b, oh, ntohs(oh->length));
+    error = ofputil_decode_queue_get_config_reply(&b, &port);
+    if (error) {
+        ofp_print_error(string, error);
+        return;
+    }
+
+    ds_put_cstr(string, " port=");
+    ofputil_format_port(port, string);
+    ds_put_char(string, '\n');
+
+    for (;;) {
+        struct ofputil_queue_config queue;
+        int retval;
+
+        retval = ofputil_pull_queue_get_config_reply(&b, &queue);
+        if (retval) {
+            if (retval != EOF) {
+                ofp_print_error(string, retval);
+            }
+            break;
+        }
+
+        ds_put_format(string, "queue %"PRIu32":", queue.queue_id);
+        print_queue_rate(string, "min_rate", queue.min_rate);
+        print_queue_rate(string, "max_rate", queue.max_rate);
+        ds_put_char(string, '\n');
+    }
+}
+
 static void
 ofp_print_meter_flags(struct ds *s, uint16_t flags)
 {
@@ -1230,12 +1368,13 @@ ofp_print_error_msg(struct ds *string, const struct ofp_header *oh)
     ds_put_format(string, " %s\n", ofperr_get_name(error));
 
     if (error == OFPERR_OFPHFC_INCOMPATIBLE || error == OFPERR_OFPHFC_EPERM) {
-        ds_put_printable(string, payload.data, payload.size);
+        ds_put_printable(string, ofpbuf_data(&payload), ofpbuf_size(&payload));
     } else {
-        s = ofp_to_string(payload.data, payload.size, 1);
+        s = ofp_to_string(ofpbuf_data(&payload), ofpbuf_size(&payload), 1);
         ds_put_cstr(string, s);
         free(s);
     }
+    ofpbuf_uninit(&payload);
 }
 
 static void
@@ -1335,6 +1474,7 @@ ofp_print_flow_stats(struct ds *string, struct ofputil_flow_stats *fs)
         ds_put_char(string, ' ');
     }
 
+    ds_put_cstr(string, "actions=");
     ofpacts_format(fs->ofpacts, fs->ofpacts_len, string);
 }
 
@@ -1418,7 +1558,7 @@ ofp_print_ofpst_port_reply(struct ds *string, const struct ofp_header *oh,
 {
     struct ofpbuf b;
 
-    ds_put_format(string, " %zu ports\n", ofputil_count_port_stats(oh));
+    ds_put_format(string, " %"PRIuSIZE" ports\n", ofputil_count_port_stats(oh));
     if (verbosity < 1) {
         return;
     }
@@ -1531,8 +1671,8 @@ ofp_print_ofpst_table_reply13(struct ds *string, const struct ofp_header *oh,
     ofpbuf_use_const(&b, oh, ntohs(oh->length));
     ofpraw_pull_assert(&b);
 
-    n = b.size / sizeof *ts;
-    ds_put_format(string, " %zu tables\n", n);
+    n = ofpbuf_size(&b) / sizeof *ts;
+    ds_put_format(string, " %"PRIuSIZE" tables\n", n);
     if (verbosity < 1) {
         return;
     }
@@ -1561,8 +1701,8 @@ ofp_print_ofpst_table_reply12(struct ds *string, const struct ofp_header *oh,
     ofpbuf_use_const(&b, oh, ntohs(oh->length));
     ofpraw_pull_assert(&b);
 
-    n = b.size / sizeof *ts;
-    ds_put_format(string, " %zu tables\n", n);
+    n = ofpbuf_size(&b) / sizeof *ts;
+    ds_put_format(string, " %"PRIuSIZE" tables\n", n);
     if (verbosity < 1) {
         return;
     }
@@ -1588,8 +1728,8 @@ ofp_print_ofpst_table_reply11(struct ds *string, const struct ofp_header *oh,
     ofpbuf_use_const(&b, oh, ntohs(oh->length));
     ofpraw_pull_assert(&b);
 
-    n = b.size / sizeof *ts;
-    ds_put_format(string, " %zu tables\n", n);
+    n = ofpbuf_size(&b) / sizeof *ts;
+    ds_put_format(string, " %"PRIuSIZE" tables\n", n);
     if (verbosity < 1) {
         return;
     }
@@ -1628,8 +1768,8 @@ ofp_print_ofpst_table_reply10(struct ds *string, const struct ofp_header *oh,
     ofpbuf_use_const(&b, oh, ntohs(oh->length));
     ofpraw_pull_assert(&b);
 
-    n = b.size / sizeof *ts;
-    ds_put_format(string, " %zu tables\n", n);
+    n = ofpbuf_size(&b) / sizeof *ts;
+    ds_put_format(string, " %"PRIuSIZE" tables\n", n);
     if (verbosity < 1) {
         return;
     }
@@ -1657,6 +1797,7 @@ ofp_print_ofpst_table_reply(struct ds *string, const struct ofp_header *oh,
                             int verbosity)
 {
     switch ((enum ofp_version)oh->version) {
+    case OFP14_VERSION:
     case OFP13_VERSION:
         ofp_print_ofpst_table_reply13(string, oh, verbosity);
         break;
@@ -1674,7 +1815,7 @@ ofp_print_ofpst_table_reply(struct ds *string, const struct ofp_header *oh,
         break;
 
     default:
-        NOT_REACHED();
+        OVS_NOT_REACHED();
     }
 }
 
@@ -1713,7 +1854,7 @@ ofp_print_ofpst_queue_reply(struct ds *string, const struct ofp_header *oh,
 {
     struct ofpbuf b;
 
-    ds_put_format(string, " %zu queues\n", ofputil_count_queue_stats(oh));
+    ds_put_format(string, " %"PRIuSIZE" queues\n", ofputil_count_queue_stats(oh));
     if (verbosity < 1) {
         return;
     }
@@ -1796,27 +1937,19 @@ ofp_print_echo(struct ds *string, const struct ofp_header *oh, int verbosity)
 {
     size_t len = ntohs(oh->length);
 
-    ds_put_format(string, " %zu bytes of payload\n", len - sizeof *oh);
+    ds_put_format(string, " %"PRIuSIZE" bytes of payload\n", len - sizeof *oh);
     if (verbosity > 1) {
         ds_put_hex_dump(string, oh + 1, len - sizeof *oh, 0, true);
     }
 }
 
 static void
-ofp_print_role_message(struct ds *string, const struct ofp_header *oh)
+ofp_print_role_generic(struct ds *string, enum ofp12_controller_role role,
+                       uint64_t generation_id)
 {
-    struct ofputil_role_request rr;
-    enum ofperr error;
-
-    error = ofputil_decode_role_message(oh, &rr);
-    if (error) {
-        ofp_print_error(string, error);
-        return;
-    }
-
     ds_put_cstr(string, " role=");
 
-    switch (rr.role) {
+    switch (role) {
     case OFPCR12_ROLE_NOCHANGE:
         ds_put_cstr(string, "nochange");
         break;
@@ -1830,11 +1963,57 @@ ofp_print_role_message(struct ds *string, const struct ofp_header *oh)
         ds_put_cstr(string, "slave");
         break;
     default:
-        NOT_REACHED();
+        OVS_NOT_REACHED();
     }
 
-    if (rr.have_generation_id) {
-        ds_put_format(string, " generation_id=%"PRIu64, rr.generation_id);
+    if (generation_id != UINT64_MAX) {
+        ds_put_format(string, " generation_id=%"PRIu64, generation_id);
+    }
+}
+
+static void
+ofp_print_role_message(struct ds *string, const struct ofp_header *oh)
+{
+    struct ofputil_role_request rr;
+    enum ofperr error;
+
+    error = ofputil_decode_role_message(oh, &rr);
+    if (error) {
+        ofp_print_error(string, error);
+        return;
+    }
+
+    ofp_print_role_generic(string, rr.role, rr.have_generation_id ? rr.generation_id : UINT64_MAX);
+}
+
+static void
+ofp_print_role_status_message(struct ds *string, const struct ofp_header *oh)
+{
+    struct ofputil_role_status rs;
+    enum ofperr error;
+
+    error = ofputil_decode_role_status(oh, &rs);
+    if (error) {
+        ofp_print_error(string, error);
+        return;
+    }
+
+    ofp_print_role_generic(string, rs.role, rs.generation_id);
+
+    ds_put_cstr(string, " reason=");
+
+    switch (rs.reason) {
+    case OFPCRR_MASTER_REQUEST:
+        ds_put_cstr(string, "master_request");
+        break;
+    case OFPCRR_CONFIG:
+        ds_put_cstr(string, "configuration_changed");
+        break;
+    case OFPCRR_EXPERIMENTER:
+        ds_put_cstr(string, "experimenter_data_changed");
+        break;
+    default:
+        OVS_NOT_REACHED();
     }
 }
 
@@ -2094,6 +2273,7 @@ ofp_print_nxst_flow_monitor_reply(struct ds *string,
             if (string->string[string->length - 1] != ' ') {
                 ds_put_char(string, ' ');
             }
+            ds_put_cstr(string, "actions=");
             ofpacts_format(update.ofpacts, update.ofpacts_len, string);
         }
     }
@@ -2115,6 +2295,9 @@ ofp_print_version(const struct ofp_header *oh,
     case OFP13_VERSION:
         ds_put_cstr(string, " (OF1.3)");
         break;
+    case OFP14_VERSION:
+        ds_put_cstr(string, " (OF1.4)");
+        break;
     default:
         ds_put_format(string, " (OF 0x%02"PRIx8")", oh->version);
         break;
@@ -2131,9 +2314,358 @@ ofp_header_to_string__(const struct ofp_header *oh, enum ofpraw raw,
 }
 
 static void
-ofp_print_not_implemented(struct ds *string)
+ofp_print_group(struct ds *s, uint32_t group_id, uint8_t type,
+                struct list *p_buckets)
 {
-    ds_put_cstr(string, "NOT IMPLEMENTED YET!\n");
+    static const char *type_str[] = { "all", "select", "indirect",
+                                      "ff", "unknown" };
+    struct ofputil_bucket *bucket;
+
+    ds_put_format(s, "group_id=%"PRIu32",type=%s",
+                  group_id, type_str[type > 4 ? 4 : type]);
+    if (!p_buckets) {
+        return;
+    }
+
+    LIST_FOR_EACH (bucket, list_node, p_buckets) {
+        ds_put_cstr(s, ",bucket=");
+
+        if (bucket->weight != 1) {
+            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) {
+            ds_put_format(s, "watch_group:%"PRIu32",", bucket->watch_group);
+        }
+
+        ds_put_cstr(s, "actions=");
+        ofpacts_format(bucket->ofpacts, bucket->ofpacts_len, s);
+    }
+}
+
+static void
+ofp_print_group_desc(struct ds *s, const struct ofp_header *oh)
+{
+    struct ofpbuf b;
+
+    ofpbuf_use_const(&b, oh, ntohs(oh->length));
+    for (;;) {
+        struct ofputil_group_desc gd;
+        int retval;
+
+        retval = ofputil_decode_group_desc_reply(&gd, &b, oh->version);
+        if (retval) {
+            if (retval != EOF) {
+                ds_put_cstr(s, " ***parse error***");
+            }
+            break;
+        }
+
+        ds_put_char(s, '\n');
+        ds_put_char(s, ' ');
+        ofp_print_group(s, gd.group_id, gd.type, &gd.buckets);
+     }
+}
+
+static void
+ofp_print_ofpst_group_request(struct ds *string, const struct ofp_header *oh)
+{
+    enum ofperr error;
+    uint32_t group_id;
+
+    error = ofputil_decode_group_stats_request(oh, &group_id);
+    if (error) {
+        ofp_print_error(string, error);
+        return;
+    }
+
+    ds_put_cstr(string, " group_id=");
+    ofputil_format_group(group_id, string);
+}
+
+static void
+ofp_print_group_stats(struct ds *s, const struct ofp_header *oh)
+{
+    struct ofpbuf b;
+    uint32_t bucket_i;
+
+    ofpbuf_use_const(&b, oh, ntohs(oh->length));
+
+    for (;;) {
+        struct ofputil_group_stats gs;
+        int retval;
+
+        retval = ofputil_decode_group_stats_reply(&b, &gs);
+        if (retval) {
+            if (retval != EOF) {
+                ds_put_cstr(s, " ***parse error***");
+            }
+            break;
+        }
+
+        ds_put_char(s, '\n');
+
+        ds_put_char(s, ' ');
+        ds_put_format(s, "group_id=%"PRIu32",", gs.group_id);
+
+        if (gs.duration_sec != UINT32_MAX) {
+            ds_put_cstr(s, "duration=");
+            ofp_print_duration(s, gs.duration_sec, gs.duration_nsec);
+            ds_put_char(s, ',');
+        }
+        ds_put_format(s, "ref_count=%"PRIu32",", gs.ref_count);
+        ds_put_format(s, "packet_count=%"PRIu64",", gs.packet_count);
+        ds_put_format(s, "byte_count=%"PRIu64"", gs.byte_count);
+
+        for (bucket_i = 0; bucket_i < gs.n_buckets; bucket_i++) {
+            if (gs.bucket_stats[bucket_i].packet_count != UINT64_MAX) {
+                ds_put_format(s, ",bucket%"PRIu32":", bucket_i);
+                ds_put_format(s, "packet_count=%"PRIu64",", gs.bucket_stats[bucket_i].packet_count);
+                ds_put_format(s, "byte_count=%"PRIu64"", gs.bucket_stats[bucket_i].byte_count);
+            }
+        }
+
+        free(gs.bucket_stats);
+     }
+}
+
+static void
+ofp_print_group_features(struct ds *string, const struct ofp_header *oh)
+{
+    struct ofputil_group_features features;
+
+    ofputil_decode_group_features_reply(oh, &features);
+
+    ds_put_format(string, "\n Group table:\n");
+    ds_put_format(string, "    Types:  0x%"PRIx32"\n", features.types);
+    ds_put_format(string, "    Capabilities:  0x%"PRIx32"\n",
+                  features.capabilities);
+
+    if (features.types & (1u << OFPGT11_ALL)) {
+        ds_put_format(string, "    All group :\n");
+        ds_put_format(string,
+                      "        max_groups = %#"PRIx32" actions=0x%08"PRIx32"\n",
+                      features.max_groups[0], features.actions[0]);
+    }
+
+    if (features.types & (1u << OFPGT11_SELECT)) {
+        ds_put_format(string, "    Select group :\n");
+        ds_put_format(string, "        max_groups = %#"PRIx32" "
+                      "actions=0x%08"PRIx32"\n",
+                      features.max_groups[1], features.actions[1]);
+    }
+
+    if (features.types & (1u << OFPGT11_INDIRECT)) {
+        ds_put_format(string, "    Indirect group :\n");
+        ds_put_format(string, "        max_groups = %#"PRIx32" "
+                      "actions=0x%08"PRIx32"\n",
+                      features.max_groups[2], features.actions[2]);
+    }
+
+    if (features.types & (1u << OFPGT11_FF)) {
+        ds_put_format(string, "    Fast Failover group :\n");
+        ds_put_format(string, "        max_groups = %#"PRIx32" "
+                      "actions=0x%08"PRIx32"\n",
+                      features.max_groups[3], features.actions[3]);
+    }
+}
+
+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;
+    }
+
+    ds_put_char(s, '\n');
+
+    ds_put_char(s, ' ');
+    switch (gm.command) {
+    case OFPGC11_ADD:
+        ds_put_cstr(s, "ADD");
+        break;
+
+    case OFPGC11_MODIFY:
+        ds_put_cstr(s, "MOD");
+        break;
+
+    case OFPGC11_DELETE:
+        ds_put_cstr(s, "DEL");
+        break;
+
+    default:
+        ds_put_format(s, "cmd:%"PRIu16"", gm.command);
+    }
+    ds_put_char(s, ' ');
+
+    ofp_print_group(s, gm.group_id, gm.type, &gm.buckets);
+}
+
+static const char *
+ofp13_action_to_string(uint32_t bit)
+{
+    switch (bit) {
+#define OFPAT13_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME)  \
+        case 1u << ENUM: return NAME;
+#include "ofp-util.def"
+    }
+    return NULL;
+}
+
+static void
+print_table_action_features(struct ds *s,
+                            const struct ofputil_table_action_features *taf)
+{
+    ds_put_cstr(s, "        actions: ");
+    ofp_print_bit_names(s, taf->actions, ofp13_action_to_string, ',');
+    ds_put_char(s, '\n');
+
+    ds_put_cstr(s, "        supported on Set-Field: ");
+    if (taf->set_fields) {
+        int i;
+
+        for (i = 0; i < MFF_N_IDS; i++) {
+            uint64_t bit = UINT64_C(1) << i;
+
+            if (taf->set_fields & bit) {
+                ds_put_format(s, "%s,", mf_from_id(i)->name);
+            }
+        }
+        ds_chomp(s, ',');
+    } else {
+        ds_put_cstr(s, "none");
+    }
+    ds_put_char(s, '\n');
+}
+
+static bool
+table_action_features_equal(const struct ofputil_table_action_features *a,
+                            const struct ofputil_table_action_features *b)
+{
+    return a->actions == b->actions && a->set_fields == b->set_fields;
+}
+
+static void
+print_table_instruction_features(
+    struct ds *s, const struct ofputil_table_instruction_features *tif)
+{
+    int start, end;
+
+    ds_put_cstr(s, "      next tables: ");
+    for (start = bitmap_scan(tif->next, 1, 0, 255); start < 255;
+         start = bitmap_scan(tif->next, 1, end, 255)) {
+        end = bitmap_scan(tif->next, 0, start + 1, 255);
+        if (end == start + 1) {
+            ds_put_format(s, "%d,", start);
+        } else {
+            ds_put_format(s, "%d-%d,", start, end - 1);
+        }
+    }
+    ds_chomp(s, ',');
+    if (ds_last(s) == ' ') {
+        ds_put_cstr(s, "none");
+    }
+    ds_put_char(s, '\n');
+
+    ds_put_cstr(s, "      instructions: ");
+    if (tif->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));
+            }
+        }
+        ds_chomp(s, ',');
+    } else {
+        ds_put_cstr(s, "none");
+    }
+    ds_put_char(s, '\n');
+
+    if (table_action_features_equal(&tif->write, &tif->apply)) {
+        ds_put_cstr(s, "      Write-Actions and Apply-Actions features:\n");
+        print_table_action_features(s, &tif->write);
+    } else {
+        ds_put_cstr(s, "      Write-Actions features:\n");
+        print_table_action_features(s, &tif->write);
+        ds_put_cstr(s, "      Apply-Actions features:\n");
+        print_table_action_features(s, &tif->apply);
+    }
+}
+
+static bool
+table_instruction_features_equal(
+    const struct ofputil_table_instruction_features *a,
+    const struct ofputil_table_instruction_features *b)
+{
+    return (bitmap_equal(a->next, b->next, 255)
+            && a->instructions == b->instructions
+            && table_action_features_equal(&a->write, &b->write)
+            && table_action_features_equal(&a->apply, &b->apply));
+}
+
+static void
+ofp_print_table_features(struct ds *s, const struct ofp_header *oh)
+{
+    struct ofpbuf b;
+
+    ofpbuf_use_const(&b, oh, ntohs(oh->length));
+
+    for (;;) {
+        struct ofputil_table_features tf;
+        int retval;
+        int i;
+
+        retval = ofputil_decode_table_features(&b, &tf, true);
+        if (retval) {
+            if (retval != EOF) {
+                ofp_print_error(s, retval);
+            }
+            return;
+        }
+
+        ds_put_format(s, "\n  table %"PRIu8":\n", tf.table_id);
+        ds_put_format(s, "    name=\"%s\"\n", tf.name);
+        ds_put_format(s, "    metadata: match=%#"PRIx64" write=%#"PRIx64"\n",
+                      ntohll(tf.metadata_match), ntohll(tf.metadata_write));
+
+        ds_put_cstr(s, "    config=");
+        ofp_print_table_miss_config(s, tf.config);
+
+        ds_put_format(s, "    max_entries=%"PRIu32"\n", tf.max_entries);
+
+        if (table_instruction_features_equal(&tf.nonmiss, &tf.miss)) {
+            ds_put_cstr(s, "    instructions (table miss and others):\n");
+            print_table_instruction_features(s, &tf.nonmiss);
+        } else {
+            ds_put_cstr(s, "    instructions (other than table miss):\n");
+            print_table_instruction_features(s, &tf.nonmiss);
+            ds_put_cstr(s, "    instructions (table miss):\n");
+            print_table_instruction_features(s, &tf.miss);
+        }
+
+        ds_put_cstr(s, "    matching:\n");
+        for (i = 0; i < MFF_N_IDS; i++) {
+            uint64_t bit = UINT64_C(1) << i;
+
+            if (tf.match & bit) {
+                const struct mf_field *f = mf_from_id(i);
+
+                ds_put_format(s, "      %s: %s\n",
+                              f->name,
+                              (tf.mask ? "arbitrary mask"
+                               : tf.wildcard ? "exact match or wildcard"
+                               : "must exact match"));
+            }
+        }
+    }
 }
 
 static void
@@ -2145,20 +2677,38 @@ ofp_to_string__(const struct ofp_header *oh, enum ofpraw raw,
     ofp_header_to_string__(oh, raw, string);
     switch (ofptype_from_ofpraw(raw)) {
 
-        /* FIXME: Change the following once they are implemented: */
-    case OFPTYPE_QUEUE_GET_CONFIG_REQUEST:
-    case OFPTYPE_QUEUE_GET_CONFIG_REPLY:
-    case OFPTYPE_GET_ASYNC_REQUEST:
-    case OFPTYPE_GET_ASYNC_REPLY:
     case OFPTYPE_GROUP_STATS_REQUEST:
+        ofp_print_stats_request(string, oh);
+        ofp_print_ofpst_group_request(string, oh);
+        break;
+
     case OFPTYPE_GROUP_STATS_REPLY:
+        ofp_print_group_stats(string, oh);
+        break;
+
     case OFPTYPE_GROUP_DESC_STATS_REQUEST:
+        ofp_print_stats_request(string, oh);
+        break;
+
     case OFPTYPE_GROUP_DESC_STATS_REPLY:
+        ofp_print_group_desc(string, oh);
+        break;
+
     case OFPTYPE_GROUP_FEATURES_STATS_REQUEST:
+        ofp_print_stats_request(string, oh);
+        break;
+
     case OFPTYPE_GROUP_FEATURES_STATS_REPLY:
+        ofp_print_group_features(string, oh);
+        break;
+
+    case OFPTYPE_GROUP_MOD:
+        ofp_print_group_mod(string, oh);
+        break;
+
     case OFPTYPE_TABLE_FEATURES_STATS_REQUEST:
     case OFPTYPE_TABLE_FEATURES_STATS_REPLY:
-        ofp_print_not_implemented(string);
+        ofp_print_table_features(string, oh);
         break;
 
     case OFPTYPE_HELLO:
@@ -2213,6 +2763,10 @@ ofp_to_string__(const struct ofp_header *oh, enum ofpraw raw,
         ofp_print_port_mod(string, oh);
         break;
 
+    case OFPTYPE_TABLE_MOD:
+        ofp_print_table_mod(string, oh);
+        break;
+
     case OFPTYPE_METER_MOD:
         ofp_print_meter_mod(string, oh);
         break;
@@ -2221,10 +2775,21 @@ ofp_to_string__(const struct ofp_header *oh, enum ofpraw raw,
     case OFPTYPE_BARRIER_REPLY:
         break;
 
+    case OFPTYPE_QUEUE_GET_CONFIG_REQUEST:
+        ofp_print_queue_get_config_request(string, oh);
+        break;
+
+    case OFPTYPE_QUEUE_GET_CONFIG_REPLY:
+        ofp_print_queue_get_config_reply(string, oh);
+        break;
+
     case OFPTYPE_ROLE_REQUEST:
     case OFPTYPE_ROLE_REPLY:
         ofp_print_role_message(string, oh);
         break;
+    case OFPTYPE_ROLE_STATUS:
+        ofp_print_role_status_message(string, oh);
+        break;
 
     case OFPTYPE_METER_STATS_REQUEST:
     case OFPTYPE_METER_CONFIG_STATS_REQUEST:
@@ -2327,10 +2892,12 @@ ofp_to_string__(const struct ofp_header *oh, enum ofpraw raw,
         ofp_print_nxt_set_controller_id(string, ofpmsg_body(oh));
         break;
 
+    case OFPTYPE_GET_ASYNC_REPLY:
     case OFPTYPE_SET_ASYNC_CONFIG:
         ofp_print_nxt_set_async_config(string, ofpmsg_body(oh));
         break;
-
+    case OFPTYPE_GET_ASYNC_REQUEST:
+        break;
     case OFPTYPE_FLOW_MONITOR_CANCEL:
         ofp_print_nxt_flow_monitor_cancel(string, msg);
         break;
@@ -2362,7 +2929,7 @@ ofp_to_string(const void *oh_, size_t len, int verbosity)
     if (!len) {
         ds_put_cstr(&string, "OpenFlow message is empty\n");
     } else if (len < sizeof(struct ofp_header)) {
-        ds_put_format(&string, "OpenFlow packet too short (only %zu bytes):\n",
+        ds_put_format(&string, "OpenFlow packet too short (only %"PRIuSIZE" bytes):\n",
                       len);
     } else if (ntohs(oh->length) > len) {
         enum ofperr error;
@@ -2375,11 +2942,11 @@ ofp_to_string(const void *oh_, size_t len, int verbosity)
         }
 
         ds_put_format(&string,
-                      "(***truncated to %zu bytes from %"PRIu16"***)\n",
+                      "(***truncated to %"PRIuSIZE" bytes from %"PRIu16"***)\n",
                       len, ntohs(oh->length));
     } else if (ntohs(oh->length) < len) {
         ds_put_format(&string,
-                      "(***only uses %"PRIu16" bytes out of %zu***)\n",
+                      "(***only uses %"PRIu16" bytes out of %"PRIuSIZE"***)\n",
                       ntohs(oh->length), len);
     } else {
         enum ofperr error;