diff --git a/include/openvswitch/netdev.h b/include/openvswitch/netdev.h
index e25c241f4c57..0c10f7b487cf 100644
--- a/include/openvswitch/netdev.h
+++ b/include/openvswitch/netdev.h
@@ -26,6 +26,7 @@ extern "C" {
 #endif
 
 struct netdev;
+struct ds;
 
 /* Maximum name length for custom statistics counters */
 #define NETDEV_CUSTOM_STATS_NAME_SIZE 64
@@ -129,6 +130,7 @@ uint64_t netdev_features_to_bps(enum netdev_features features,
                                 uint64_t default_bps);
 bool netdev_features_is_full_duplex(enum netdev_features features);
 int netdev_set_advertisements(struct netdev *, enum netdev_features advertise);
+void netdev_features_format(struct ds *, enum netdev_features);
 
 void netdev_free_custom_stats_counters(struct netdev_custom_stats *);
 
diff --git a/include/openvswitch/ofp-errors.h b/include/openvswitch/ofp-errors.h
index 6542f10b46fd..16feacb559b4 100644
--- a/include/openvswitch/ofp-errors.h
+++ b/include/openvswitch/ofp-errors.h
@@ -29,6 +29,8 @@ extern "C" {
 
 struct ds;
 struct ofpbuf;
+struct ofputil_port_map;
+struct ofputil_table_map;
 
 /* Error codes.
  *
@@ -898,6 +900,10 @@ enum ofperr ofperr_decode_msg(const struct ofp_header *,
 struct ofpbuf *ofperr_encode_reply(enum ofperr, const struct ofp_header *);
 struct ofpbuf *ofperr_encode_hello(enum ofperr, enum ofp_version ofp_version,
                                    const char *);
+void ofperr_msg_format(struct ds *, enum ofperr, const struct ofpbuf *payload,
+                  const struct ofputil_port_map *,
+                  const struct ofputil_table_map *);
+
 int ofperr_get_vendor(enum ofperr, enum ofp_version);
 int ofperr_get_type(enum ofperr, enum ofp_version);
 int ofperr_get_code(enum ofperr, enum ofp_version);
diff --git a/include/openvswitch/ofp-flow.h b/include/openvswitch/ofp-flow.h
index 28aa77bad820..17d48f12e060 100644
--- a/include/openvswitch/ofp-flow.h
+++ b/include/openvswitch/ofp-flow.h
@@ -64,6 +64,8 @@ enum ofputil_flow_mod_flags {
                                           to be modified */
 };
 
+void ofputil_flow_mod_flags_format(struct ds *, enum ofputil_flow_mod_flags);
+
 /* Protocol-independent flow_mod.
  *
  * The handling of cookies across multiple versions of OpenFlow is a bit
@@ -123,6 +125,10 @@ enum ofperr ofputil_decode_flow_mod(struct ofputil_flow_mod *,
                                     uint8_t max_table);
 struct ofpbuf *ofputil_encode_flow_mod(const struct ofputil_flow_mod *,
                                        enum ofputil_protocol);
+enum ofperr ofputil_flow_mod_format(struct ds *, const struct ofp_header *,
+                                    const struct ofputil_port_map *,
+                                    const struct ofputil_table_map *,
+                                    int verbosity);
 
 char *parse_ofp_str(struct ofputil_flow_mod *, int command, const char *str_,
                     const struct ofputil_port_map *,
@@ -172,6 +178,10 @@ char *parse_ofp_flow_stats_request_str(struct ofputil_flow_stats_request *,
                                        enum ofputil_protocol *usable_protocols)
     OVS_WARN_UNUSED_RESULT;
 
+void ofputil_flow_stats_request_format(
+    struct ds *, const struct ofputil_flow_stats_request *,
+    const struct ofputil_port_map *, const struct ofputil_table_map *);
+
 /* Flow stats reply, independent of protocol. */
 struct ofputil_flow_stats {
     struct match match;
@@ -200,6 +210,11 @@ void ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *,
                                      struct ovs_list *replies,
                                      const struct tun_table *);
 
+void ofputil_flow_stats_format(struct ds *, const struct ofputil_flow_stats *,
+                               const struct ofputil_port_map *,
+                               const struct ofputil_table_map *,
+                               bool show_stats);
+
 /* Aggregate stats reply, independent of protocol. */
 struct ofputil_aggregate_stats {
     uint64_t packet_count;      /* Packet count, UINT64_MAX if unknown. */
@@ -213,6 +228,8 @@ struct ofpbuf *ofputil_encode_aggregate_stats_reply(
 enum ofperr ofputil_decode_aggregate_stats_reply(
     struct ofputil_aggregate_stats *,
     const struct ofp_header *reply);
+void ofputil_aggregate_stats_format(struct ds *,
+                                    const struct ofputil_aggregate_stats *);
 
 #ifdef __cplusplus
 }
diff --git a/include/openvswitch/ofp-match.h b/include/openvswitch/ofp-match.h
index c3baa8e8ff22..6ed373e2bf4d 100644
--- a/include/openvswitch/ofp-match.h
+++ b/include/openvswitch/ofp-match.h
@@ -24,6 +24,7 @@
 struct vl_mff_map;
 struct flow_wildcards;
 struct match;
+struct ofputil_port_map;
 struct tun_table;
 
 #ifdef __cplusplus
@@ -37,6 +38,10 @@ void ofputil_match_from_ofp10_match(const struct ofp10_match *,
 void ofputil_normalize_match(struct match *);
 void ofputil_normalize_match_quiet(struct match *);
 void ofputil_match_to_ofp10_match(const struct match *, struct ofp10_match *);
+void ofp10_match_print(struct ds *, const struct ofp10_match *,
+                       const struct ofputil_port_map *, int verbosity);
+char *ofp10_match_to_string(const struct ofp10_match *,
+                            const struct ofputil_port_map *, int verbosity);
 
 /* Work with ofp11_match. */
 enum ofperr ofputil_pull_ofp11_match(struct ofpbuf *, const struct tun_table *,
diff --git a/include/openvswitch/ofp-monitor.h b/include/openvswitch/ofp-monitor.h
index 7680440d4372..bd225518e56f 100644
--- a/include/openvswitch/ofp-monitor.h
+++ b/include/openvswitch/ofp-monitor.h
@@ -29,6 +29,9 @@ extern "C" {
 
 struct ofputil_table_map;
 
+const char *ofp_flow_removed_reason_to_string(enum ofp_flow_removed_reason,
+                                              char *reasonbuf, size_t bufsize);
+
 /* Flow removed message, independent of protocol. */
 struct ofputil_flow_removed {
     struct match match;
@@ -48,6 +51,10 @@ enum ofperr ofputil_decode_flow_removed(struct ofputil_flow_removed *,
                                         const struct ofp_header *);
 struct ofpbuf *ofputil_encode_flow_removed(const struct ofputil_flow_removed *,
                                            enum ofputil_protocol);
+void ofputil_flow_removed_format(struct ds *,
+                                 const struct ofputil_flow_removed *,
+                                 const struct ofputil_port_map *,
+                                 const struct ofputil_table_map *);
 
 /* Abstract nx_flow_monitor_request. */
 struct ofputil_flow_monitor_request {
@@ -62,6 +69,10 @@ int ofputil_decode_flow_monitor_request(struct ofputil_flow_monitor_request *,
                                         struct ofpbuf *msg);
 void ofputil_append_flow_monitor_request(
     const struct ofputil_flow_monitor_request *, struct ofpbuf *msg);
+void ofputil_flow_monitor_request_format(
+    struct ds *, const struct ofputil_flow_monitor_request *,
+    const struct ofputil_port_map *, const struct ofputil_table_map *);
+
 char *parse_flow_monitor_request(struct ofputil_flow_monitor_request *,
                                  const char *,
                                  const struct ofputil_port_map *,
@@ -94,6 +105,10 @@ void ofputil_start_flow_update(struct ovs_list *replies);
 void ofputil_append_flow_update(const struct ofputil_flow_update *,
                                 struct ovs_list *replies,
                                 const struct tun_table *);
+void ofputil_flow_update_format(struct ds *,
+                                const struct ofputil_flow_update *,
+                                const struct ofputil_port_map *,
+                                const struct ofputil_table_map *);
 
 /* Abstract nx_flow_monitor_cancel. */
 uint32_t ofputil_decode_flow_monitor_cancel(const struct ofp_header *);
@@ -120,8 +135,6 @@ enum ofperr ofputil_decode_requestforward(const struct ofp_header *,
                                           struct ofputil_requestforward *);
 void ofputil_destroy_requestforward(struct ofputil_requestforward *);
 
-
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/include/openvswitch/ofp-packet.h b/include/openvswitch/ofp-packet.h
index f96e36613ac6..67001cb3f5d0 100644
--- a/include/openvswitch/ofp-packet.h
+++ b/include/openvswitch/ofp-packet.h
@@ -154,6 +154,12 @@ enum ofperr ofputil_decode_packet_in_private(
     struct ofputil_packet_in_private *,
     size_t *total_len, uint32_t *buffer_id);
 
+void ofputil_packet_in_private_format(
+    struct ds *, const struct ofputil_packet_in_private *,
+    size_t total_len, uint32_t buffer_id,
+    const struct ofputil_port_map *,
+    const struct ofputil_table_map *, int verbosity);
+
 void ofputil_packet_in_private_destroy(struct ofputil_packet_in_private *);
 
 /* Abstract packet-out message.
@@ -176,6 +182,11 @@ enum ofperr ofputil_decode_packet_out(struct ofputil_packet_out *,
 struct ofpbuf *ofputil_encode_packet_out(const struct ofputil_packet_out *,
                                          enum ofputil_protocol protocol);
 
+void ofputil_packet_out_format(struct ds *, const struct ofputil_packet_out *,
+                               const struct ofputil_port_map *,
+                               const struct ofputil_table_map *,
+                               int verbosity);
+
 char *parse_ofp_packet_out_str(struct ofputil_packet_out *, const char *,
                                const struct ofputil_port_map *,
                                const struct ofputil_table_map *,
diff --git a/include/openvswitch/ofp-port.h b/include/openvswitch/ofp-port.h
index 59ca0373b032..4286ba19d693 100644
--- a/include/openvswitch/ofp-port.h
+++ b/include/openvswitch/ofp-port.h
@@ -19,6 +19,7 @@
 
 #include "openvswitch/hmap.h"
 #include "openvswitch/netdev.h"
+#include "openvswitch/ofp-errors.h"
 #include "openvswitch/ofp-protocol.h"
 #include "openvswitch/namemap.h"
 
@@ -71,6 +72,8 @@ enum ofputil_port_config {
     /* There are no OpenFlow 1.1-only bits. */
 };
 
+void ofputil_port_config_format(struct ds *, enum ofputil_port_config);
+
 enum ofputil_port_state {
     /* OpenFlow 1.0 and 1.1 share this values for these port state bits. */
     OFPUTIL_PS_LINK_DOWN   = 1 << 0, /* No physical link present. */
@@ -85,6 +88,8 @@ enum ofputil_port_state {
     OFPUTIL_PS_STP_MASK    = 3 << 8  /* Bit mask for OFPPS10_STP_* values. */
 };
 
+void ofputil_port_state_format(struct ds *, enum ofputil_port_state);
+
 /* Abstract ofp10_phy_port, ofp11_port, ofp14_port, or ofp16_port. */
 struct ofputil_phy_port {
     ofp_port_t port_no;
@@ -112,11 +117,13 @@ struct ofputil_phy_port {
     uint32_t max_speed;         /* Maximum supported speed, in kbps. */
 };
 
-/* phy_port helper functions. */
 void ofputil_put_phy_port(enum ofp_version,
                           const struct ofputil_phy_port *, struct ofpbuf *);
 int ofputil_pull_phy_port(enum ofp_version, struct ofpbuf *,
                           struct ofputil_phy_port *);
+void ofputil_phy_port_format(struct ds *, const struct ofputil_phy_port *);
+enum ofperr ofputil_phy_ports_format(struct ds *, uint8_t ofp_version,
+                                     struct ofpbuf *);
 
 /* Abstract ofp_port_status. */
 struct ofputil_port_status {
@@ -128,6 +135,8 @@ enum ofperr ofputil_decode_port_status(const struct ofp_header *,
                                        struct ofputil_port_status *);
 struct ofpbuf *ofputil_encode_port_status(const struct ofputil_port_status *,
                                           enum ofputil_protocol);
+void ofputil_port_status_format(struct ds *,
+                                const struct ofputil_port_status *);
 
 /* Abstract ofp_port_mod. */
 struct ofputil_port_mod {
@@ -143,6 +152,8 @@ enum ofperr ofputil_decode_port_mod(const struct ofp_header *,
                                     struct ofputil_port_mod *, bool loose);
 struct ofpbuf *ofputil_encode_port_mod(const struct ofputil_port_mod *,
                                        enum ofputil_protocol);
+void ofputil_port_mod_format(struct ds *, const struct ofputil_port_mod *,
+                             const struct ofputil_port_map *);
 
 struct ofputil_port_stats {
     ofp_port_t port_no;
diff --git a/include/openvswitch/ofp-print.h b/include/openvswitch/ofp-print.h
index ed113786a28c..d76f06872701 100644
--- a/include/openvswitch/ofp-print.h
+++ b/include/openvswitch/ofp-print.h
@@ -25,7 +25,6 @@
 #include <openvswitch/types.h>
 
 struct ds;
-struct ofp10_match;
 struct ofp_flow_mod;
 struct ofp_header;
 struct ofputil_flow_stats;
@@ -45,28 +44,17 @@ void ofp_print_packet(FILE *stream, const void *data,
                       size_t len, ovs_be32 packet_type);
 void ofp_print_dp_packet(FILE *stream, const struct dp_packet *packet);
 
-void ofp10_match_print(struct ds *, const struct ofp10_match *,
-                       const struct ofputil_port_map *, int verbosity);
-
 char *ofp_to_string(const void *, size_t, const struct ofputil_port_map *,
                     const struct ofputil_table_map *, int verbosity);
-char *ofp10_match_to_string(const struct ofp10_match *,
-                            const struct ofputil_port_map *, int verbosity);
 char *ofp_packet_to_string(const void *data, size_t len, ovs_be32 packet_type);
 char *ofp_dp_packet_to_string(const struct dp_packet *packet);
 
 void ofp_print_version(const struct ofp_header *, struct ds *);
-void ofp_print_table_features(
-    struct ds *, const struct ofputil_table_features *features,
-    const struct ofputil_table_features *prev_features,
-    const struct ofputil_table_stats *stats,
-    const struct ofputil_table_stats *prev_stats,
-    const struct ofputil_table_map *table_map);
-
-void ofp_print_flow_stats(struct ds *, const struct ofputil_flow_stats *,
-                          const struct ofputil_port_map *,
-                          const struct ofputil_table_map *,
-                          bool show_stats);
+
+void ofp_print_duration(struct ds *, unsigned int sec, unsigned int nsec);
+void ofp_print_bit_names(struct ds *, uint32_t bits,
+                         const char *(*bit_to_name)(uint32_t bit),
+                         char separator);
 
 #ifdef  __cplusplus
 }
diff --git a/include/openvswitch/ofp-switch.h b/include/openvswitch/ofp-switch.h
index 737cd61e707f..6f75a2533596 100644
--- a/include/openvswitch/ofp-switch.h
+++ b/include/openvswitch/ofp-switch.h
@@ -71,6 +71,8 @@ struct ofpbuf *ofputil_encode_switch_features(
     ovs_be32 xid);
 void ofputil_put_switch_features_port(const struct ofputil_phy_port *,
                                       struct ofpbuf *);
+void ofputil_switch_features_format(struct ds *,
+                                    const struct ofputil_switch_features *);
 bool ofputil_switch_features_has_ports(struct ofpbuf *b);
 
 enum ofputil_frag_handling {
@@ -108,6 +110,9 @@ enum ofperr ofputil_decode_set_config(const struct ofp_header *,
 struct ofpbuf *ofputil_encode_set_config(
     const struct ofputil_switch_config *, enum ofp_version);
 
+void ofputil_switch_config_format(struct ds *,
+                                  const struct ofputil_switch_config *);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/include/openvswitch/ofp-table.h b/include/openvswitch/ofp-table.h
index 47b0fae80763..e8260657f486 100644
--- a/include/openvswitch/ofp-table.h
+++ b/include/openvswitch/ofp-table.h
@@ -26,6 +26,8 @@
 extern "C" {
 #endif
 
+struct ofputil_table_stats;
+
 /* Abstract version of OFPTC11_TABLE_MISS_*.
  *
  * OpenFlow 1.0 always sends packets that miss to the next flow table, or to
@@ -52,6 +54,8 @@ enum ofputil_table_miss {
     OFPUTIL_TABLE_MISS_DROP,       /* Drop the packet. */
 };
 
+const char *ofputil_table_miss_to_string(enum ofputil_table_miss);
+
 /* Abstract version of OFPTC14_EVICTION.
  *
  * OpenFlow 1.0 through 1.3 don't know anything about eviction, so decoding a
@@ -63,6 +67,8 @@ enum ofputil_table_eviction {
     OFPUTIL_TABLE_EVICTION_OFF      /* Disable eviction. */
 };
 
+const char *ofputil_table_eviction_to_string(enum ofputil_table_eviction);
+
 /* Abstract version of OFPTC14_VACANCY_EVENTS.
  *
  * OpenFlow 1.0 through 1.3 don't know anything about vacancy events, so
@@ -74,6 +80,8 @@ enum ofputil_table_vacancy {
     OFPUTIL_TABLE_VACANCY_OFF      /* Disable vacancy events. */
 };
 
+const char *ofputil_table_vacancy_to_string(enum ofputil_table_vacancy);
+
 /* Abstract version of OFPTMPT_VACANCY.
  *
  * Openflow 1.4+ defines vacancy events.
@@ -141,25 +149,37 @@ struct ofputil_table_mod {
     struct ofputil_table_mod_prop_vacancy table_vacancy;
 };
 
-/* Abstract ofp14_table_desc. */
-struct ofputil_table_desc {
-    uint8_t table_id;         /* ID of the table. */
-    enum ofputil_table_eviction eviction;
-    uint32_t eviction_flags;    /* UINT32_MAX if not present. */
-    enum ofputil_table_vacancy vacancy;
-    struct ofputil_table_mod_prop_vacancy table_vacancy;
-};
-
 enum ofperr ofputil_decode_table_mod(const struct ofp_header *,
                                     struct ofputil_table_mod *);
 struct ofpbuf *ofputil_encode_table_mod(const struct ofputil_table_mod *,
                                        enum ofputil_protocol);
+void ofputil_table_mod_format(struct ds *, const struct ofputil_table_mod *,
+                              const struct ofputil_table_map *);
 char *parse_ofp_table_mod(struct ofputil_table_mod *,
                           const char *table_id, const char *flow_miss_handling,
                           const struct ofputil_table_map *,
                           uint32_t *usable_versions)
     OVS_WARN_UNUSED_RESULT;
 
+/* Abstract ofp14_table_desc. */
+struct ofputil_table_desc {
+    uint8_t table_id;         /* ID of the table. */
+    enum ofputil_table_eviction eviction;
+    uint32_t eviction_flags;    /* UINT32_MAX if not present. */
+    enum ofputil_table_vacancy vacancy;
+    struct ofputil_table_mod_prop_vacancy table_vacancy;
+};
+
+int ofputil_decode_table_desc(struct ofpbuf *,
+                              struct ofputil_table_desc *,
+                              enum ofp_version);
+void ofputil_append_table_desc_reply(const struct ofputil_table_desc *td,
+                                     struct ovs_list *replies,
+                                     enum ofp_version);
+void ofputil_table_desc_format(struct ds *,
+                               const struct ofputil_table_desc *,
+                               const struct ofputil_table_map *);
+
 /* Abstract ofp_table_features.
  *
  * This is used for all versions of OpenFlow, even though ofp_table_features
@@ -244,10 +264,6 @@ struct ofputil_table_features {
 int ofputil_decode_table_features(struct ofpbuf *,
                                   struct ofputil_table_features *, bool loose);
 
-int ofputil_decode_table_desc(struct ofpbuf *,
-                              struct ofputil_table_desc *,
-                              enum ofp_version);
-
 struct ofpbuf *ofputil_encode_table_features_request(enum ofp_version);
 
 struct ofpbuf *ofputil_encode_table_desc_request(enum ofp_version);
@@ -255,9 +271,12 @@ struct ofpbuf *ofputil_encode_table_desc_request(enum ofp_version);
 void ofputil_append_table_features_reply(
     const struct ofputil_table_features *tf, struct ovs_list *replies);
 
-void ofputil_append_table_desc_reply(const struct ofputil_table_desc *td,
-                                     struct ovs_list *replies,
-                                     enum ofp_version);
+void ofputil_table_features_format(
+    struct ds *, const struct ofputil_table_features *features,
+    const struct ofputil_table_features *prev_features,
+    const struct ofputil_table_stats *stats,
+    const struct ofputil_table_stats *prev_stats,
+    const struct ofputil_table_map *table_map);
 
 /* Abstract table stats.
  *
diff --git a/include/openvswitch/ofp-util.h b/include/openvswitch/ofp-util.h
index 1f4b23eb5190..091a09cad3f1 100644
--- a/include/openvswitch/ofp-util.h
+++ b/include/openvswitch/ofp-util.h
@@ -30,6 +30,7 @@ extern "C" {
 bool ofputil_decode_hello(const struct ofp_header *,
                           uint32_t *allowed_versions);
 struct ofpbuf *ofputil_encode_hello(uint32_t version_bitmap);
+void ofputil_hello_format(struct ds *, const struct ofp_header *);
 
 struct ofpbuf *ofputil_encode_echo_request(enum ofp_version);
 struct ofpbuf *ofputil_encode_echo_reply(const struct ofp_header *);
diff --git a/lib/netdev.c b/lib/netdev.c
index be05dc64024a..94f94814849e 100644
--- a/lib/netdev.c
+++ b/lib/netdev.c
@@ -45,6 +45,7 @@
 #include "odp-netlink.h"
 #include "openflow/openflow.h"
 #include "packets.h"
+#include "openvswitch/ofp-print.h"
 #include "openvswitch/poll-loop.h"
 #include "seq.h"
 #include "openvswitch/shash.h"
@@ -1095,6 +1096,40 @@ netdev_set_advertisements(struct netdev *netdev,
             : EOPNOTSUPP);
 }
 
+static const char *
+netdev_feature_to_name(uint32_t bit)
+{
+    enum netdev_features f = bit;
+
+    switch (f) {
+    case NETDEV_F_10MB_HD:    return "10MB-HD";
+    case NETDEV_F_10MB_FD:    return "10MB-FD";
+    case NETDEV_F_100MB_HD:   return "100MB-HD";
+    case NETDEV_F_100MB_FD:   return "100MB-FD";
+    case NETDEV_F_1GB_HD:     return "1GB-HD";
+    case NETDEV_F_1GB_FD:     return "1GB-FD";
+    case NETDEV_F_10GB_FD:    return "10GB-FD";
+    case NETDEV_F_40GB_FD:    return "40GB-FD";
+    case NETDEV_F_100GB_FD:   return "100GB-FD";
+    case NETDEV_F_1TB_FD:     return "1TB-FD";
+    case NETDEV_F_OTHER:      return "OTHER";
+    case NETDEV_F_COPPER:     return "COPPER";
+    case NETDEV_F_FIBER:      return "FIBER";
+    case NETDEV_F_AUTONEG:    return "AUTO_NEG";
+    case NETDEV_F_PAUSE:      return "AUTO_PAUSE";
+    case NETDEV_F_PAUSE_ASYM: return "AUTO_PAUSE_ASYM";
+    }
+
+    return NULL;
+}
+
+void
+netdev_features_format(struct ds *s, enum netdev_features features)
+{
+    ofp_print_bit_names(s, features, netdev_feature_to_name, ' ');
+    ds_put_char(s, '\n');
+}
+
 /* Assigns 'addr' as 'netdev''s IPv4 address and 'mask' as its netmask.  If
  * 'addr' is INADDR_ANY, 'netdev''s IPv4 address is cleared.  Returns a
  * positive errno value. */
diff --git a/lib/ofp-errors.c b/lib/ofp-errors.c
index bdae00c74958..3ca09cb6886c 100644
--- a/lib/ofp-errors.c
+++ b/lib/ofp-errors.c
@@ -22,6 +22,7 @@
 #include "openvswitch/dynamic-string.h"
 #include "openvswitch/ofp-errors.h"
 #include "openvswitch/ofp-msgs.h"
+#include "openvswitch/ofp-print.h"
 #include "openvswitch/ofpbuf.h"
 #include "openvswitch/vlog.h"
 #include "util.h"
@@ -240,6 +241,24 @@ ofperr_encode_hello(enum ofperr error, enum ofp_version ofp_version,
     return ofperr_encode_msg__(error, ofp_version, htonl(0), s, strlen(s));
 }
 
+void
+ofperr_msg_format(struct ds *string, enum ofperr error,
+                  const struct ofpbuf *payload,
+                  const struct ofputil_port_map *port_map,
+                  const struct ofputil_table_map *table_map)
+{
+    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);
+    } else {
+        char *s = ofp_to_string(payload->data, payload->size,
+                                port_map, table_map, 1);
+        ds_put_cstr(string, s);
+        free(s);
+    }
+}
+
 int
 ofperr_get_vendor(enum ofperr error, enum ofp_version version)
 {
diff --git a/lib/ofp-flow.c b/lib/ofp-flow.c
index 10d6825991f6..cffadb30381b 100644
--- a/lib/ofp-flow.c
+++ b/lib/ofp-flow.c
@@ -18,6 +18,7 @@
 #include "openvswitch/ofp-flow.h"
 #include <errno.h>
 #include "byte-order.h"
+#include "colors.h"
 #include "flow.h"
 #include "nx-match.h"
 #include "openvswitch/ofp-actions.h"
@@ -26,6 +27,7 @@
 #include "openvswitch/ofp-msgs.h"
 #include "openvswitch/ofp-parse.h"
 #include "openvswitch/ofp-port.h"
+#include "openvswitch/ofp-print.h"
 #include "openvswitch/ofp-table.h"
 #include "openvswitch/ofpbuf.h"
 #include "openvswitch/vlog.h"
@@ -103,6 +105,32 @@ ofputil_encode_flow_mod_flags(enum ofputil_flow_mod_flags flags,
     return htons(raw_flags);
 }
 
+void
+ofputil_flow_mod_flags_format(struct ds *s, enum ofputil_flow_mod_flags flags)
+{
+    if (flags & OFPUTIL_FF_SEND_FLOW_REM) {
+        ds_put_cstr(s, "send_flow_rem ");
+    }
+    if (flags & OFPUTIL_FF_CHECK_OVERLAP) {
+        ds_put_cstr(s, "check_overlap ");
+    }
+    if (flags & OFPUTIL_FF_RESET_COUNTS) {
+        ds_put_cstr(s, "reset_counts ");
+    }
+    if (flags & OFPUTIL_FF_NO_PKT_COUNTS) {
+        ds_put_cstr(s, "no_packet_counts ");
+    }
+    if (flags & OFPUTIL_FF_NO_BYT_COUNTS) {
+        ds_put_cstr(s, "no_byte_counts ");
+    }
+    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 ");
+    }
+}
+
 /* Converts an OFPT_FLOW_MOD or NXT_FLOW_MOD message 'oh' into an abstract
  * flow_mod in 'fm'.  Returns 0 if successful, otherwise an OpenFlow error
  * code.
@@ -438,6 +466,133 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod *fm,
     return msg;
 }
 
+enum ofperr
+ofputil_flow_mod_format(struct ds *s, const struct ofp_header *oh,
+                        const struct ofputil_port_map *port_map,
+                        const struct ofputil_table_map *table_map,
+                        int verbosity)
+{
+    struct ofputil_flow_mod fm;
+    struct ofpbuf ofpacts;
+    bool need_priority;
+    enum ofperr error;
+    enum ofpraw raw;
+    enum ofputil_protocol protocol;
+
+    protocol = ofputil_protocol_from_ofp_version(oh->version);
+    protocol = ofputil_protocol_set_tid(protocol, true);
+
+    ofpbuf_init(&ofpacts, 64);
+    error = ofputil_decode_flow_mod(&fm, oh, protocol, NULL, NULL, &ofpacts,
+                                    OFPP_MAX, 255);
+    if (error) {
+        ofpbuf_uninit(&ofpacts);
+        return error;
+    }
+
+    ds_put_char(s, ' ');
+    switch (fm.command) {
+    case OFPFC_ADD:
+        ds_put_cstr(s, "ADD");
+        break;
+    case OFPFC_MODIFY:
+        ds_put_cstr(s, "MOD");
+        break;
+    case OFPFC_MODIFY_STRICT:
+        ds_put_cstr(s, "MOD_STRICT");
+        break;
+    case OFPFC_DELETE:
+        ds_put_cstr(s, "DEL");
+        break;
+    case OFPFC_DELETE_STRICT:
+        ds_put_cstr(s, "DEL_STRICT");
+        break;
+    default:
+        ds_put_format(s, "cmd:%d", fm.command);
+    }
+    if (fm.table_id != 0
+        || ofputil_table_map_get_name(table_map, fm.table_id)) {
+        ds_put_format(s, " table:");
+        ofputil_format_table(fm.table_id, table_map, s);
+    }
+
+    ds_put_char(s, ' ');
+    ofpraw_decode(&raw, oh);
+    if (verbosity >= 3 && raw == OFPRAW_OFPT10_FLOW_MOD) {
+        const struct ofp10_flow_mod *ofm = ofpmsg_body(oh);
+        ofp10_match_print(s, &ofm->match, port_map, verbosity);
+
+        /* ofp_print_match() doesn't print priority. */
+        need_priority = true;
+    } else if (verbosity >= 3 && raw == OFPRAW_NXT_FLOW_MOD) {
+        const struct nx_flow_mod *nfm = ofpmsg_body(oh);
+        const void *nxm = nfm + 1;
+        char *nxm_s;
+
+        nxm_s = nx_match_to_string(nxm, ntohs(nfm->match_len));
+        ds_put_cstr(s, nxm_s);
+        free(nxm_s);
+
+        /* nx_match_to_string() doesn't print priority. */
+        need_priority = true;
+    } else {
+        match_format(&fm.match, port_map, s, fm.priority);
+
+        /* match_format() does print priority. */
+        need_priority = false;
+    }
+
+    if (ds_last(s) != ' ') {
+        ds_put_char(s, ' ');
+    }
+    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)) {
+        ds_put_format(s, "cookie:0x%"PRIx64"/0x%"PRIx64" ",
+                ntohll(fm.cookie), ntohll(fm.cookie_mask));
+    }
+    if (fm.idle_timeout != OFP_FLOW_PERMANENT) {
+        ds_put_format(s, "idle:%"PRIu16" ", fm.idle_timeout);
+    }
+    if (fm.hard_timeout != OFP_FLOW_PERMANENT) {
+        ds_put_format(s, "hard:%"PRIu16" ", fm.hard_timeout);
+    }
+    if (fm.importance != 0) {
+        ds_put_format(s, "importance:%"PRIu16" ", fm.importance);
+    }
+    if (fm.priority != OFP_DEFAULT_PRIORITY && need_priority) {
+        ds_put_format(s, "pri:%d ", fm.priority);
+    }
+    if (fm.buffer_id != UINT32_MAX) {
+        ds_put_format(s, "buf:0x%"PRIx32" ", fm.buffer_id);
+    }
+    if (fm.out_port != OFPP_ANY) {
+        ds_put_format(s, "out_port:");
+        ofputil_format_port(fm.out_port, port_map, s);
+        ds_put_char(s, ' ');
+    }
+
+    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;
+    }
+    ofputil_flow_mod_flags_format(s, fm.flags);
+
+    ds_put_cstr(s, "actions=");
+    struct ofpact_format_params fp = {
+        .port_map = port_map,
+        .table_map = table_map,
+        .s = s,
+    };
+    ofpacts_format(fm.ofpacts, fm.ofpacts_len, &fp);
+    ofpbuf_uninit(&ofpacts);
+
+    return 0;
+}
+
 static enum ofperr
 ofputil_decode_ofpst10_flow_request(struct ofputil_flow_stats_request *fsr,
                                     const struct ofp10_flow_stats_request *ofsr,
@@ -629,6 +784,26 @@ ofputil_encode_flow_stats_request(const struct ofputil_flow_stats_request *fsr,
     return msg;
 }
 
+void
+ofputil_flow_stats_request_format(struct ds *s,
+                                  const struct ofputil_flow_stats_request *fsr,
+                                  const struct ofputil_port_map *port_map,
+                                  const struct ofputil_table_map *table_map)
+{
+    if (fsr->table_id != 0xff) {
+        ds_put_format(s, " table=");
+        ofputil_format_table(fsr->table_id, table_map, s);
+    }
+
+    if (fsr->out_port != OFPP_ANY) {
+        ds_put_cstr(s, " out_port=");
+        ofputil_format_port(fsr->out_port, port_map, s);
+    }
+
+    ds_put_char(s, ' ');
+    match_format(&fsr->match, port_map, s, OFP_DEFAULT_PRIORITY);
+}
+
 char * OVS_WARN_UNUSED_RESULT
 parse_ofp_flow_stats_request_str(struct ofputil_flow_stats_request *fsr,
                                  bool aggregate, const char *string,
@@ -969,6 +1144,80 @@ ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *fs,
     fs_->match.flow.tunnel.metadata.tab = orig_tun_table;
 }
 
+/* Appends a textual form of 'fs' to 'string', translating port numbers to
+ * names using 'port_map' (if provided).  If 'show_stats' is true, the output
+ * includes the flow duration, packet and byte counts, and its idle and hard
+ * ages, otherwise they are omitted. */
+void
+ofputil_flow_stats_format(struct ds *string,
+                          const struct ofputil_flow_stats *fs,
+                          const struct ofputil_port_map *port_map,
+                          const struct ofputil_table_map *table_map,
+                          bool show_stats)
+{
+    if (show_stats || fs->cookie) {
+        ds_put_format(string, "%scookie=%s0x%"PRIx64", ",
+                      colors.param, colors.end, ntohll(fs->cookie));
+    }
+    if (show_stats) {
+        ds_put_format(string, "%sduration=%s", colors.param, colors.end);
+        ofp_print_duration(string, fs->duration_sec, fs->duration_nsec);
+        ds_put_cstr(string, ", ");
+    }
+
+    if (show_stats || fs->table_id
+        || ofputil_table_map_get_name(table_map, fs->table_id) != NULL) {
+        ds_put_format(string, "%stable=%s", colors.special, colors.end);
+        ofputil_format_table(fs->table_id, table_map, string);
+        ds_put_cstr(string, ", ");
+    }
+    if (show_stats) {
+        ds_put_format(string, "%sn_packets=%s%"PRIu64", ",
+                      colors.param, colors.end, fs->packet_count);
+        ds_put_format(string, "%sn_bytes=%s%"PRIu64", ",
+                      colors.param, colors.end, fs->byte_count);
+    }
+    if (fs->idle_timeout != OFP_FLOW_PERMANENT) {
+        ds_put_format(string, "%sidle_timeout=%s%"PRIu16", ",
+                      colors.param, colors.end, fs->idle_timeout);
+    }
+    if (fs->hard_timeout != OFP_FLOW_PERMANENT) {
+        ds_put_format(string, "%shard_timeout=%s%"PRIu16", ",
+                      colors.param, colors.end, fs->hard_timeout);
+    }
+    if (fs->flags) {
+        ofputil_flow_mod_flags_format(string, fs->flags);
+    }
+    if (fs->importance != 0) {
+        ds_put_format(string, "%simportance=%s%"PRIu16", ",
+                      colors.param, colors.end, fs->importance);
+    }
+    if (show_stats && fs->idle_age >= 0) {
+        ds_put_format(string, "%sidle_age=%s%d, ",
+                      colors.param, colors.end, fs->idle_age);
+    }
+    if (show_stats && fs->hard_age >= 0 && fs->hard_age != fs->duration_sec) {
+        ds_put_format(string, "%shard_age=%s%d, ",
+                      colors.param, colors.end, fs->hard_age);
+    }
+
+    /* Print the match, followed by a space (but omit the space if the match
+     * was an empty string). */
+    size_t length = string->length;
+    match_format(&fs->match, port_map, string, fs->priority);
+    if (string->length != length) {
+        ds_put_char(string, ' ');
+    }
+
+    ds_put_format(string, "%sactions=%s", colors.actions, colors.end);
+    struct ofpact_format_params fp = {
+        .port_map = port_map,
+        .table_map = table_map,
+        .s = string,
+    };
+    ofpacts_format(fs->ofpacts, fs->ofpacts_len, &fp);
+}
+
 /* Converts abstract ofputil_aggregate_stats 'stats' into an OFPST_AGGREGATE or
  * NXST_AGGREGATE reply matching 'request', and returns the message. */
 struct ofpbuf *
@@ -1015,6 +1264,14 @@ ofputil_decode_aggregate_stats_reply(struct ofputil_aggregate_stats *stats,
     return 0;
 }
 
+void
+ofputil_aggregate_stats_format(struct ds *s,
+                               const struct ofputil_aggregate_stats *as)
+{
+    ds_put_format(s, " packet_count=%"PRIu64, as->packet_count);
+    ds_put_format(s, " byte_count=%"PRIu64, as->byte_count);
+    ds_put_format(s, " flow_count=%"PRIu32, as->flow_count);
+}
 
 /* Parses 'str_value' as the value of subfield 'name', and updates
  * 'match' appropriately.  Restricts the set of usable protocols to ones
diff --git a/lib/ofp-match.c b/lib/ofp-match.c
index c907f2908cb2..acdf0b776640 100644
--- a/lib/ofp-match.c
+++ b/lib/ofp-match.c
@@ -953,3 +953,157 @@ ofputil_normalize_match_quiet(struct match *match)
 {
     ofputil_normalize_match__(match, false);
 }
+
+static void OVS_PRINTF_FORMAT(5, 6)
+print_wild(struct ds *string, const char *leader, int is_wild,
+           int verbosity, const char *format, ...)
+{
+    if (is_wild && verbosity < 2) {
+        return;
+    }
+    ds_put_cstr(string, leader);
+    if (!is_wild) {
+        va_list args;
+
+        va_start(args, format);
+        ds_put_format_valist(string, format, args);
+        va_end(args);
+    } else {
+        ds_put_char(string, '*');
+    }
+    ds_put_char(string, ',');
+}
+
+static void
+print_wild_port(struct ds *string, const char *leader, int is_wild,
+                int verbosity, ofp_port_t port,
+                const struct ofputil_port_map *port_map)
+{
+    if (is_wild && verbosity < 2) {
+        return;
+    }
+    ds_put_cstr(string, leader);
+    if (!is_wild) {
+        ofputil_format_port(port, port_map, string);
+    } else {
+        ds_put_char(string, '*');
+    }
+    ds_put_char(string, ',');
+}
+
+static void
+print_ip_netmask(struct ds *string, const char *leader, ovs_be32 ip,
+                 uint32_t wild_bits, int verbosity)
+{
+    if (wild_bits >= 32 && verbosity < 2) {
+        return;
+    }
+    ds_put_cstr(string, leader);
+    if (wild_bits < 32) {
+        ds_put_format(string, IP_FMT, IP_ARGS(ip));
+        if (wild_bits) {
+            ds_put_format(string, "/%d", 32 - wild_bits);
+        }
+    } else {
+        ds_put_char(string, '*');
+    }
+    ds_put_char(string, ',');
+}
+
+void
+ofp10_match_print(struct ds *f, const struct ofp10_match *om,
+                  const struct ofputil_port_map *port_map, int verbosity)
+{
+    char *s = ofp10_match_to_string(om, port_map, verbosity);
+    ds_put_cstr(f, s);
+    free(s);
+}
+
+char *
+ofp10_match_to_string(const struct ofp10_match *om,
+                      const struct ofputil_port_map *port_map, int verbosity)
+{
+    struct ds f = DS_EMPTY_INITIALIZER;
+    uint32_t w = ntohl(om->wildcards);
+    bool skip_type = false;
+    bool skip_proto = false;
+
+    if (!(w & OFPFW10_DL_TYPE)) {
+        skip_type = true;
+        if (om->dl_type == htons(ETH_TYPE_IP)) {
+            if (!(w & OFPFW10_NW_PROTO)) {
+                skip_proto = true;
+                if (om->nw_proto == IPPROTO_ICMP) {
+                    ds_put_cstr(&f, "icmp,");
+                } else if (om->nw_proto == IPPROTO_TCP) {
+                    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;
+                }
+            } else {
+                ds_put_cstr(&f, "ip,");
+            }
+        } else if (om->dl_type == htons(ETH_TYPE_ARP)) {
+            ds_put_cstr(&f, "arp,");
+        } else if (om->dl_type == htons(ETH_TYPE_RARP)){
+            ds_put_cstr(&f, "rarp,");
+        } else if (om->dl_type == htons(ETH_TYPE_MPLS)) {
+            ds_put_cstr(&f, "mpls,");
+        } else if (om->dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
+            ds_put_cstr(&f, "mplsm,");
+        } else {
+            skip_type = false;
+        }
+    }
+    print_wild_port(&f, "in_port=", w & OFPFW10_IN_PORT, verbosity,
+                    u16_to_ofp(ntohs(om->in_port)), port_map);
+    print_wild(&f, "dl_vlan=", w & OFPFW10_DL_VLAN, verbosity,
+               "%d", ntohs(om->dl_vlan));
+    print_wild(&f, "dl_vlan_pcp=", w & OFPFW10_DL_VLAN_PCP, verbosity,
+               "%d", om->dl_vlan_pcp);
+    print_wild(&f, "dl_src=", w & OFPFW10_DL_SRC, verbosity,
+               ETH_ADDR_FMT, ETH_ADDR_ARGS(om->dl_src));
+    print_wild(&f, "dl_dst=", w & OFPFW10_DL_DST, verbosity,
+               ETH_ADDR_FMT, ETH_ADDR_ARGS(om->dl_dst));
+    if (!skip_type) {
+        print_wild(&f, "dl_type=", w & OFPFW10_DL_TYPE, verbosity,
+                   "0x%04x", ntohs(om->dl_type));
+    }
+    print_ip_netmask(&f, "nw_src=", om->nw_src,
+                     (w & OFPFW10_NW_SRC_MASK) >> OFPFW10_NW_SRC_SHIFT,
+                     verbosity);
+    print_ip_netmask(&f, "nw_dst=", om->nw_dst,
+                     (w & OFPFW10_NW_DST_MASK) >> OFPFW10_NW_DST_SHIFT,
+                     verbosity);
+    if (!skip_proto) {
+        if (om->dl_type == htons(ETH_TYPE_ARP) ||
+            om->dl_type == htons(ETH_TYPE_RARP)) {
+            print_wild(&f, "arp_op=", w & OFPFW10_NW_PROTO, verbosity,
+                       "%u", om->nw_proto);
+        } else {
+            print_wild(&f, "nw_proto=", w & OFPFW10_NW_PROTO, verbosity,
+                       "%u", om->nw_proto);
+        }
+    }
+    print_wild(&f, "nw_tos=", w & OFPFW10_NW_TOS, verbosity,
+               "%u", om->nw_tos);
+    if (om->nw_proto == IPPROTO_ICMP) {
+        print_wild(&f, "icmp_type=", w & OFPFW10_ICMP_TYPE, verbosity,
+                   "%d", ntohs(om->tp_src));
+        print_wild(&f, "icmp_code=", w & OFPFW10_ICMP_CODE, verbosity,
+                   "%d", ntohs(om->tp_dst));
+    } else {
+        print_wild(&f, "tp_src=", w & OFPFW10_TP_SRC, verbosity,
+                   "%d", ntohs(om->tp_src));
+        print_wild(&f, "tp_dst=", w & OFPFW10_TP_DST, verbosity,
+                   "%d", ntohs(om->tp_dst));
+    }
+    ds_chomp(&f, ',');
+    return ds_cstr(&f);
+}
+
diff --git a/lib/ofp-monitor.c b/lib/ofp-monitor.c
index 49d623c35cff..dd2a6bbeb9ec 100644
--- a/lib/ofp-monitor.c
+++ b/lib/ofp-monitor.c
@@ -26,6 +26,7 @@
 #include "openvswitch/ofp-meter.h"
 #include "openvswitch/ofp-msgs.h"
 #include "openvswitch/ofp-parse.h"
+#include "openvswitch/ofp-print.h"
 #include "openvswitch/ofp-table.h"
 #include "openvswitch/vlog.h"
 
@@ -33,6 +34,34 @@ VLOG_DEFINE_THIS_MODULE(ofp_monitor);
 
 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
 
+/* 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_FLOW_REMOVED_REASON_BUFSIZE. */
+#define OFP_FLOW_REMOVED_REASON_BUFSIZE (INT_STRLEN(int) + 1)
+const char *
+ofp_flow_removed_reason_to_string(enum ofp_flow_removed_reason reason,
+                                  char *reasonbuf, size_t bufsize)
+{
+    switch (reason) {
+    case OFPRR_IDLE_TIMEOUT:
+        return "idle";
+    case OFPRR_HARD_TIMEOUT:
+        return "hard";
+    case OFPRR_DELETE:
+        return "delete";
+    case OFPRR_GROUP_DELETE:
+        return "group_delete";
+    case OFPRR_EVICTION:
+        return "eviction";
+    case OFPRR_METER_DELETE:
+        return "meter_delete";
+    case OVS_OFPRR_NONE:
+    default:
+        snprintf(reasonbuf, bufsize, "%d", (int) reason);
+        return reasonbuf;
+    }
+}
+
 /* Converts an OFPT_FLOW_REMOVED or NXT_FLOW_REMOVED message 'oh' into an
  * abstract ofputil_flow_removed in 'fr'.  Returns 0 if successful, otherwise
  * an OpenFlow error code. */
@@ -211,6 +240,41 @@ ofputil_encode_flow_removed(const struct ofputil_flow_removed *fr,
 
     return msg;
 }
+
+void
+ofputil_flow_removed_format(struct ds *s,
+                            const struct ofputil_flow_removed *fr,
+                            const struct ofputil_port_map *port_map,
+                            const struct ofputil_table_map *table_map)
+{
+    char reasonbuf[OFP_FLOW_REMOVED_REASON_BUFSIZE];
+
+    ds_put_char(s, ' ');
+    match_format(&fr->match, port_map, s, fr->priority);
+
+    ds_put_format(s, " reason=%s",
+                  ofp_flow_removed_reason_to_string(fr->reason, reasonbuf,
+                                                    sizeof reasonbuf));
+
+    if (fr->table_id != 255) {
+        ds_put_format(s, " table_id=");
+        ofputil_format_table(fr->table_id, table_map, s);
+    }
+
+    if (fr->cookie != htonll(0)) {
+        ds_put_format(s, " cookie:0x%"PRIx64, ntohll(fr->cookie));
+    }
+    ds_put_cstr(s, " duration");
+    ofp_print_duration(s, fr->duration_sec, fr->duration_nsec);
+    ds_put_format(s, " idle%"PRIu16, fr->idle_timeout);
+    if (fr->hard_timeout) {
+        /* The hard timeout was only added in OF1.2, so only print it if it is
+         * actually in use to avoid gratuitous change to the formatting. */
+        ds_put_format(s, " hard%"PRIu16, fr->hard_timeout);
+    }
+    ds_put_format(s, " pkts%"PRIu64" bytes%"PRIu64"\n",
+                  fr->packet_count, fr->byte_count);
+}
 
 /* ofputil_flow_monitor_request */
 
@@ -291,6 +355,47 @@ ofputil_append_flow_monitor_request(
     nfmr->table_id = rq->table_id;
 }
 
+static const char *
+nx_flow_monitor_flags_to_name(uint32_t bit)
+{
+    enum nx_flow_monitor_flags fmf = bit;
+
+    switch (fmf) {
+    case NXFMF_INITIAL: return "initial";
+    case NXFMF_ADD: return "add";
+    case NXFMF_DELETE: return "delete";
+    case NXFMF_MODIFY: return "modify";
+    case NXFMF_ACTIONS: return "actions";
+    case NXFMF_OWN: return "own";
+    }
+
+    return NULL;
+}
+
+void
+ofputil_flow_monitor_request_format(
+    struct ds *s, const struct ofputil_flow_monitor_request *request,
+    const struct ofputil_port_map *port_map,
+    const struct ofputil_table_map *table_map)
+{
+    ds_put_format(s, "\n id=%"PRIu32" flags=", request->id);
+    ofp_print_bit_names(s, request->flags, nx_flow_monitor_flags_to_name, ',');
+
+    if (request->out_port != OFPP_NONE) {
+        ds_put_cstr(s, " out_port=");
+        ofputil_format_port(request->out_port, port_map, s);
+    }
+
+    if (request->table_id != 0xff) {
+        ds_put_format(s, " table=");
+        ofputil_format_table(request->table_id, table_map, s);
+    }
+
+    ds_put_char(s, ' ');
+    match_format(&request->match, port_map, s, OFP_DEFAULT_PRIORITY);
+    ds_chomp(s, ' ');
+}
+
 static char * OVS_WARN_UNUSED_RESULT
 parse_flow_monitor_request__(struct ofputil_flow_monitor_request *fmr,
                              const char *str_,
@@ -567,6 +672,63 @@ ofputil_append_flow_update(const struct ofputil_flow_update *update,
     ofpmp_postappend(replies, start_ofs);
     update_->match.flow.tunnel.metadata.tab = orig_tun_table;
 }
+
+void
+ofputil_flow_update_format(struct ds *s,
+                           const struct ofputil_flow_update *update,
+                           const struct ofputil_port_map *port_map,
+                           const struct ofputil_table_map *table_map)
+{
+    char reasonbuf[OFP_FLOW_REMOVED_REASON_BUFSIZE];
+
+    ds_put_cstr(s, "\n event=");
+    switch (update->event) {
+    case NXFME_ADDED:
+        ds_put_cstr(s, "ADDED");
+        break;
+
+    case NXFME_DELETED:
+        ds_put_format(s, "DELETED reason=%s",
+                      ofp_flow_removed_reason_to_string(update->reason,
+                                                        reasonbuf,
+                                                        sizeof reasonbuf));
+        break;
+
+    case NXFME_MODIFIED:
+        ds_put_cstr(s, "MODIFIED");
+        break;
+
+    case NXFME_ABBREV:
+        ds_put_format(s, "ABBREV xid=0x%"PRIx32, ntohl(update->xid));
+        return;
+    }
+
+    ds_put_format(s, " table=");
+    ofputil_format_table(update->table_id, table_map, s);
+    if (update->idle_timeout != OFP_FLOW_PERMANENT) {
+        ds_put_format(s, " idle_timeout=%"PRIu16, update->idle_timeout);
+    }
+    if (update->hard_timeout != OFP_FLOW_PERMANENT) {
+        ds_put_format(s, " hard_timeout=%"PRIu16, update->hard_timeout);
+    }
+    ds_put_format(s, " cookie=%#"PRIx64, ntohll(update->cookie));
+
+    ds_put_char(s, ' ');
+    match_format(&update->match, port_map, s, OFP_DEFAULT_PRIORITY);
+
+    if (update->ofpacts_len) {
+        if (s->string[s->length - 1] != ' ') {
+            ds_put_char(s, ' ');
+        }
+        ds_put_cstr(s, "actions=");
+        struct ofpact_format_params fp = {
+            .port_map = port_map,
+            .table_map = table_map,
+            .s = s,
+        };
+        ofpacts_format(update->ofpacts, update->ofpacts_len, &fp);
+    }
+}
 
 /* Encodes 'rf' according to 'protocol', and returns the encoded message.
  * 'protocol' must be for OpenFlow 1.4 or later. */
diff --git a/lib/ofp-packet.c b/lib/ofp-packet.c
index 100f7c5693d9..b74c29b612a9 100644
--- a/lib/ofp-packet.c
+++ b/lib/ofp-packet.c
@@ -23,8 +23,10 @@
 #include "openvswitch/ofp-errors.h"
 #include "openvswitch/ofp-msgs.h"
 #include "openvswitch/ofp-parse.h"
+#include "openvswitch/ofp-print.h"
 #include "openvswitch/ofp-port.h"
 #include "openvswitch/ofp-prop.h"
+#include "openvswitch/ofp-table.h"
 #include "openvswitch/ofpbuf.h"
 #include "openvswitch/vlog.h"
 #include "util.h"
@@ -895,6 +897,130 @@ ofputil_decode_packet_in_private(const struct ofp_header *oh, bool loose,
     return error;
 }
 
+static void
+format_hex_arg(struct ds *s, const uint8_t *data, size_t len)
+{
+    for (size_t i = 0; i < len; i++) {
+        if (i) {
+            ds_put_char(s, '.');
+        }
+        ds_put_format(s, "%02"PRIx8, data[i]);
+    }
+}
+
+void
+ofputil_packet_in_private_format(struct ds *s,
+                                 const struct ofputil_packet_in_private *pin,
+                                 size_t total_len, uint32_t buffer_id,
+                                 const struct ofputil_port_map *port_map,
+                                 const struct ofputil_table_map *table_map,
+                                 int verbosity)
+{
+    char reasonbuf[OFPUTIL_PACKET_IN_REASON_BUFSIZE];
+    const struct ofputil_packet_in *public = &pin->base;
+
+    if (public->table_id
+        || ofputil_table_map_get_name(table_map, public->table_id)) {
+        ds_put_format(s, " table_id=");
+        ofputil_format_table(public->table_id, table_map, s);
+    }
+
+    if (public->cookie != OVS_BE64_MAX) {
+        ds_put_format(s, " cookie=0x%"PRIx64, ntohll(public->cookie));
+    }
+
+    ds_put_format(s, " total_len=%"PRIuSIZE" ", total_len);
+
+    match_format(&public->flow_metadata, port_map, s, OFP_DEFAULT_PRIORITY);
+
+    ds_put_format(s, " (via %s)",
+                  ofputil_packet_in_reason_to_string(public->reason,
+                                                     reasonbuf,
+                                                     sizeof reasonbuf));
+
+    ds_put_format(s, " data_len=%"PRIuSIZE, public->packet_len);
+    if (buffer_id == UINT32_MAX) {
+        ds_put_format(s, " (unbuffered)");
+        if (total_len != public->packet_len) {
+            ds_put_format(s, " (***total_len != data_len***)");
+        }
+    } else {
+        ds_put_format(s, " buffer=0x%08"PRIx32, buffer_id);
+        if (total_len < public->packet_len) {
+            ds_put_format(s, " (***total_len < data_len***)");
+        }
+    }
+    ds_put_char(s, '\n');
+
+    if (public->userdata_len) {
+        ds_put_cstr(s, " userdata=");
+        format_hex_arg(s, pin->base.userdata, pin->base.userdata_len);
+        ds_put_char(s, '\n');
+    }
+
+    if (!uuid_is_zero(&pin->bridge)) {
+        ds_put_format(s, " continuation.bridge="UUID_FMT"\n",
+                      UUID_ARGS(&pin->bridge));
+    }
+
+    if (pin->stack_size) {
+        ds_put_cstr(s, " continuation.stack=(top)");
+
+        struct ofpbuf pin_stack;
+        ofpbuf_use_const(&pin_stack, pin->stack, pin->stack_size);
+
+        while (pin_stack.size) {
+            uint8_t len;
+            uint8_t *val = nx_stack_pop(&pin_stack, &len);
+            union mf_subvalue value;
+
+            ds_put_char(s, ' ');
+            memset(&value, 0, sizeof value - len);
+            memcpy(&value.u8[sizeof value - len], val, len);
+            mf_subvalue_format(&value, s);
+        }
+        ds_put_cstr(s, " (bottom)\n");
+    }
+
+    if (pin->mirrors) {
+        ds_put_format(s, " continuation.mirrors=0x%"PRIx32"\n",
+                      pin->mirrors);
+    }
+
+    if (pin->conntracked) {
+        ds_put_cstr(s, " continuation.conntracked=true\n");
+    }
+
+    struct ofpact_format_params fp = {
+        .port_map = port_map,
+        .table_map = table_map,
+        .s = s,
+    };
+
+    if (pin->actions_len) {
+        ds_put_cstr(s, " continuation.actions=");
+        ofpacts_format(pin->actions, pin->actions_len, &fp);
+        ds_put_char(s, '\n');
+    }
+
+    if (pin->action_set_len) {
+        ds_put_cstr(s, " continuation.action_set=");
+        ofpacts_format(pin->action_set, pin->action_set_len, &fp);
+        ds_put_char(s, '\n');
+    }
+
+    if (verbosity > 0) {
+        char *packet = ofp_packet_to_string(
+            public->packet, public->packet_len,
+            public->flow_metadata.flow.packet_type);
+        ds_put_cstr(s, packet);
+        free(packet);
+    }
+    if (verbosity > 2) {
+        ds_put_hex_dump(s, public->packet, public->packet_len, 0, false);
+    }
+}
+
 /* Frees data in 'pin' that is dynamically allocated by
  * ofputil_decode_packet_in_private().
  *
@@ -1095,6 +1221,41 @@ ofputil_encode_packet_out(const struct ofputil_packet_out *po,
 
     return msg;
 }
+
+void
+ofputil_packet_out_format(struct ds *s, const struct ofputil_packet_out *po,
+                          const struct ofputil_port_map *port_map,
+                          const struct ofputil_table_map *table_map,
+                          int verbosity)
+{
+    ds_put_char(s, ' ');
+    match_format(&po->flow_metadata, port_map, s, OFP_DEFAULT_PRIORITY);
+
+    ds_put_cstr(s, " actions=");
+    struct ofpact_format_params fp = {
+        .port_map = port_map,
+        .table_map = table_map,
+        .s = s,
+    };
+    ofpacts_format(po->ofpacts, po->ofpacts_len, &fp);
+
+    if (po->buffer_id == UINT32_MAX) {
+        ds_put_format(s, " data_len=%"PRIuSIZE, po->packet_len);
+        if (verbosity > 0 && po->packet_len > 0) {
+            ovs_be32 po_packet_type = po->flow_metadata.flow.packet_type;
+            char *packet = ofp_packet_to_string(po->packet, po->packet_len,
+                                                po_packet_type);
+            ds_put_char(s, '\n');
+            ds_put_cstr(s, packet);
+            free(packet);
+        }
+        if (verbosity > 2) {
+            ds_put_hex_dump(s, po->packet, po->packet_len, 0, false);
+        }
+    } else {
+        ds_put_format(s, " buffer=0x%08"PRIx32, po->buffer_id);
+    }
+}
 
 /* Parse a string representation of a OFPT_PACKET_OUT to '*po'.  If successful,
  * both 'po->ofpacts' and 'po->packet' must be free()d by the caller. */
diff --git a/lib/ofp-port.c b/lib/ofp-port.c
index 86562c9b47a5..ce171ef64eeb 100644
--- a/lib/ofp-port.c
+++ b/lib/ofp-port.c
@@ -16,12 +16,14 @@
 
 #include <config.h>
 #include "openvswitch/ofp-port.h"
+#include <ctype.h>
 #include "byte-order.h"
 #include "flow.h"
 #include "openflow/intel-ext.h"
 #include "openvswitch/json.h"
 #include "openvswitch/ofp-errors.h"
 #include "openvswitch/ofp-msgs.h"
+#include "openvswitch/ofp-print.h"
 #include "openvswitch/ofp-prop.h"
 #include "openvswitch/ofpbuf.h"
 #include "openvswitch/vlog.h"
@@ -284,6 +286,83 @@ ofputil_port_to_string(ofp_port_t port,
     snprintf(namebuf, bufsize, "%"PRIu32, port);
 }
 
+/* ofputil_port_config */
+
+static const char *
+ofputil_port_config_to_name(uint32_t bit)
+{
+    enum ofputil_port_config pc = bit;
+
+    switch (pc) {
+    case OFPUTIL_PC_PORT_DOWN:    return "PORT_DOWN";
+    case OFPUTIL_PC_NO_STP:       return "NO_STP";
+    case OFPUTIL_PC_NO_RECV:      return "NO_RECV";
+    case OFPUTIL_PC_NO_RECV_STP:  return "NO_RECV_STP";
+    case OFPUTIL_PC_NO_FLOOD:     return "NO_FLOOD";
+    case OFPUTIL_PC_NO_FWD:       return "NO_FWD";
+    case OFPUTIL_PC_NO_PACKET_IN: return "NO_PACKET_IN";
+    }
+
+    return NULL;
+}
+
+void
+ofputil_port_config_format(struct ds *s, enum ofputil_port_config config)
+{
+    ofp_print_bit_names(s, config, ofputil_port_config_to_name, ' ');
+    ds_put_char(s, '\n');
+}
+
+/* ofputil_port_state */
+
+static const char *
+ofputil_port_state_to_name(uint32_t bit)
+{
+    enum ofputil_port_state ps = bit;
+
+    switch (ps) {
+    case OFPUTIL_PS_LINK_DOWN: return "LINK_DOWN";
+    case OFPUTIL_PS_BLOCKED:   return "BLOCKED";
+    case OFPUTIL_PS_LIVE:      return "LIVE";
+
+    case OFPUTIL_PS_STP_LISTEN:
+    case OFPUTIL_PS_STP_LEARN:
+    case OFPUTIL_PS_STP_FORWARD:
+    case OFPUTIL_PS_STP_BLOCK:
+        /* Handled elsewhere. */
+        return NULL;
+    }
+
+    return NULL;
+}
+
+void
+ofputil_port_state_format(struct ds *s, enum ofputil_port_state state)
+{
+    enum ofputil_port_state stp_state;
+
+    /* The STP state is a 2-bit field so it doesn't fit in with the bitmask
+     * pattern.  We have to special case it.
+     *
+     * OVS doesn't support STP, so this field will always be 0 if we are
+     * talking to OVS, so we'd always print STP_LISTEN in that case.
+     * Therefore, we don't print anything at all if the value is STP_LISTEN, to
+     * avoid confusing users. */
+    stp_state = state & OFPUTIL_PS_STP_MASK;
+    if (stp_state) {
+        ds_put_cstr(s, (stp_state == OFPUTIL_PS_STP_LEARN ? "STP_LEARN"
+                        : stp_state == OFPUTIL_PS_STP_FORWARD ? "STP_FORWARD"
+                        : "STP_BLOCK"));
+        state &= ~OFPUTIL_PS_STP_MASK;
+        if (state) {
+            ofp_print_bit_names(s, state, ofputil_port_state_to_name, ' ');
+        }
+    } else {
+        ofp_print_bit_names(s, state, ofputil_port_state_to_name, ' ');
+    }
+    ds_put_char(s, '\n');
+}
+
 /* ofputil_phy_port */
 
 /* NETDEV_F_* to and from OFPPF_* and OFPPF10_*. */
@@ -722,6 +801,103 @@ ofputil_pull_phy_port(enum ofp_version ofp_version, struct ofpbuf *b,
         OVS_NOT_REACHED();
     }
 }
+
+void
+ofputil_phy_port_format(struct ds *s, const struct ofputil_phy_port *port)
+{
+    char name[sizeof port->name];
+    int j;
+
+    memcpy(name, port->name, sizeof name);
+    for (j = 0; j < sizeof name - 1; j++) {
+        if (!isprint((unsigned char) name[j])) {
+            break;
+        }
+    }
+    name[j] = '\0';
+
+    ds_put_char(s, ' ');
+    ofputil_format_port(port->port_no, NULL, s);
+    ds_put_format(s, "(%s): addr:"ETH_ADDR_FMT"\n",
+                  name, ETH_ADDR_ARGS(port->hw_addr));
+
+    if (!eth_addr64_is_zero(port->hw_addr64)) {
+        ds_put_format(s, "     addr64: "ETH_ADDR64_FMT"\n",
+                      ETH_ADDR64_ARGS(port->hw_addr64));
+    }
+
+    ds_put_cstr(s, "     config:     ");
+    ofputil_port_config_format(s, port->config);
+
+    ds_put_cstr(s, "     state:      ");
+    ofputil_port_state_format(s, port->state);
+
+    if (port->curr) {
+        ds_put_format(s, "     current:    ");
+        netdev_features_format(s, port->curr);
+    }
+    if (port->advertised) {
+        ds_put_format(s, "     advertised: ");
+        netdev_features_format(s, port->advertised);
+    }
+    if (port->supported) {
+        ds_put_format(s, "     supported:  ");
+        netdev_features_format(s, port->supported);
+    }
+    if (port->peer) {
+        ds_put_format(s, "     peer:       ");
+        netdev_features_format(s, port->peer);
+    }
+    ds_put_format(s, "     speed: %"PRIu32" Mbps now, "
+                  "%"PRIu32" Mbps max\n",
+                  port->curr_speed / UINT32_C(1000),
+                  port->max_speed / UINT32_C(1000));
+}
+
+/* qsort comparison function. */
+static int
+compare_ports(const void *a_, const void *b_)
+{
+    const struct ofputil_phy_port *a = a_;
+    const struct ofputil_phy_port *b = b_;
+    uint16_t ap = ofp_to_u16(a->port_no);
+    uint16_t bp = ofp_to_u16(b->port_no);
+
+    return ap < bp ? -1 : ap > bp;
+}
+
+/* Given a buffer 'b' that contains an array of OpenFlow ports of type
+ * 'ofp_version', writes a detailed description of each port into 'string'. */
+enum ofperr
+ofputil_phy_ports_format(struct ds *string, uint8_t ofp_version,
+                         struct ofpbuf *b)
+{
+    struct ofputil_phy_port *ports;
+    size_t allocated_ports, n_ports;
+    int retval;
+    size_t i;
+
+    ports = NULL;
+    allocated_ports = 0;
+    for (n_ports = 0; ; n_ports++) {
+        if (n_ports >= allocated_ports) {
+            ports = x2nrealloc(ports, &allocated_ports, sizeof *ports);
+        }
+
+        retval = ofputil_pull_phy_port(ofp_version, b, &ports[n_ports]);
+        if (retval) {
+            break;
+        }
+    }
+
+    qsort(ports, n_ports, sizeof *ports, compare_ports);
+    for (i = 0; i < n_ports; i++) {
+        ofputil_phy_port_format(string, &ports[i]);
+    }
+    free(ports);
+
+    return retval != EOF ? retval : 0;
+}
 
 /* ofputil_port_status */
 
@@ -792,6 +968,21 @@ ofputil_encode_port_status(const struct ofputil_port_status *ps,
     return b;
 }
 
+void
+ofputil_port_status_format(struct ds *s,
+                           const struct ofputil_port_status *ps)
+{
+    if (ps->reason == OFPPR_ADD) {
+        ds_put_format(s, " ADD:");
+    } else if (ps->reason == OFPPR_DELETE) {
+        ds_put_format(s, " DEL:");
+    } else if (ps->reason == OFPPR_MODIFY) {
+        ds_put_format(s, " MOD:");
+    }
+
+    ofputil_phy_port_format(s, &ps->desc);
+}
+
 /* ofputil_port_mod */
 
 static enum ofperr
@@ -1011,6 +1202,33 @@ ofputil_encode_port_mod(const struct ofputil_port_mod *pm,
 
     return b;
 }
+
+void
+ofputil_port_mod_format(struct ds *s, const struct ofputil_port_mod *pm,
+                        const struct ofputil_port_map *port_map)
+{
+    ds_put_cstr(s, " port: ");
+    ofputil_format_port(pm->port_no, port_map, s);
+    ds_put_format(s, ": addr:"ETH_ADDR_FMT"\n",
+                  ETH_ADDR_ARGS(pm->hw_addr));
+    if (!eth_addr64_is_zero(pm->hw_addr64)) {
+        ds_put_format(s, "     addr64: "ETH_ADDR64_FMT"\n",
+                      ETH_ADDR64_ARGS(pm->hw_addr64));
+    }
+
+    ds_put_cstr(s, "     config: ");
+    ofputil_port_config_format(s, pm->config);
+
+    ds_put_cstr(s, "     mask:   ");
+    ofputil_port_config_format(s, pm->mask);
+
+    ds_put_cstr(s, "     advertise: ");
+    if (pm->advertise) {
+        netdev_features_format(s, pm->advertise);
+    } else {
+        ds_put_cstr(s, "UNCHANGED\n");
+    }
+}
 
 /* Encode a dump ports request for 'port', the encoded message
  * will be for OpenFlow version 'ofp_version'. Returns message
diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index c0bfa92843c6..096c341c9b4d 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -117,140 +117,23 @@ ofp_dp_packet_to_string(const struct dp_packet *packet)
                                 packet->packet_type);
 }
 
-static void
-format_hex_arg(struct ds *s, const uint8_t *data, size_t len)
-{
-    for (size_t i = 0; i < len; i++) {
-        if (i) {
-            ds_put_char(s, '.');
-        }
-        ds_put_format(s, "%02"PRIx8, data[i]);
-    }
-}
-
 static enum ofperr
 ofp_print_packet_in(struct ds *string, const struct ofp_header *oh,
                     const struct ofputil_port_map *port_map,
                     const struct ofputil_table_map *table_map, int verbosity)
 {
-    char reasonbuf[OFPUTIL_PACKET_IN_REASON_BUFSIZE];
     struct ofputil_packet_in_private pin;
-    const struct ofputil_packet_in *public = &pin.base;
     uint32_t buffer_id;
     size_t total_len;
-    enum ofperr error;
-
-    error = ofputil_decode_packet_in_private(oh, true, NULL, NULL,
-                                             &pin, &total_len, &buffer_id);
-    if (error) {
-        return error;
-    }
-
-    if (public->table_id
-        || ofputil_table_map_get_name(table_map, public->table_id)) {
-        ds_put_format(string, " table_id=");
-        ofputil_format_table(public->table_id, table_map, string);
-    }
-
-    if (public->cookie != OVS_BE64_MAX) {
-        ds_put_format(string, " cookie=0x%"PRIx64, ntohll(public->cookie));
-    }
-
-    ds_put_format(string, " total_len=%"PRIuSIZE" ", total_len);
-
-    match_format(&public->flow_metadata, port_map,
-                 string, OFP_DEFAULT_PRIORITY);
-
-    ds_put_format(string, " (via %s)",
-                  ofputil_packet_in_reason_to_string(public->reason,
-                                                     reasonbuf,
-                                                     sizeof reasonbuf));
-
-    ds_put_format(string, " data_len=%"PRIuSIZE, public->packet_len);
-    if (buffer_id == UINT32_MAX) {
-        ds_put_format(string, " (unbuffered)");
-        if (total_len != public->packet_len) {
-            ds_put_format(string, " (***total_len != data_len***)");
-        }
-    } else {
-        ds_put_format(string, " buffer=0x%08"PRIx32, buffer_id);
-        if (total_len < public->packet_len) {
-            ds_put_format(string, " (***total_len < data_len***)");
-        }
-    }
-    ds_put_char(string, '\n');
-
-    if (public->userdata_len) {
-        ds_put_cstr(string, " userdata=");
-        format_hex_arg(string, pin.base.userdata, pin.base.userdata_len);
-        ds_put_char(string, '\n');
-    }
-
-    if (!uuid_is_zero(&pin.bridge)) {
-        ds_put_format(string, " continuation.bridge="UUID_FMT"\n",
-                      UUID_ARGS(&pin.bridge));
-    }
-
-    if (pin.stack_size) {
-        ds_put_cstr(string, " continuation.stack=(top)");
-
-        struct ofpbuf pin_stack;
-        ofpbuf_use_const(&pin_stack, pin.stack, pin.stack_size);
-
-        while (pin_stack.size) {
-            uint8_t len;
-            uint8_t *val = nx_stack_pop(&pin_stack, &len);
-            union mf_subvalue value;
-
-            ds_put_char(string, ' ');
-            memset(&value, 0, sizeof value - len);
-            memcpy(&value.u8[sizeof value - len], val, len);
-            mf_subvalue_format(&value, string);
-        }
-        ds_put_cstr(string, " (bottom)\n");
-    }
-
-    if (pin.mirrors) {
-        ds_put_format(string, " continuation.mirrors=0x%"PRIx32"\n",
-                      pin.mirrors);
-    }
-
-    if (pin.conntracked) {
-        ds_put_cstr(string, " continuation.conntracked=true\n");
-    }
-
-    struct ofpact_format_params fp = {
-        .port_map = port_map,
-        .table_map = table_map,
-        .s = string,
-    };
-
-    if (pin.actions_len) {
-        ds_put_cstr(string, " continuation.actions=");
-        ofpacts_format(pin.actions, pin.actions_len, &fp);
-        ds_put_char(string, '\n');
-    }
-
-    if (pin.action_set_len) {
-        ds_put_cstr(string, " continuation.action_set=");
-        ofpacts_format(pin.action_set, pin.action_set_len, &fp);
-        ds_put_char(string, '\n');
-    }
-
-    if (verbosity > 0) {
-        char *packet = ofp_packet_to_string(
-            public->packet, public->packet_len,
-            public->flow_metadata.flow.packet_type);
-        ds_put_cstr(string, packet);
-        free(packet);
-    }
-    if (verbosity > 2) {
-        ds_put_hex_dump(string, public->packet, public->packet_len, 0, false);
+    enum ofperr error = ofputil_decode_packet_in_private(oh, true, NULL, NULL,
+                                                         &pin, &total_len,
+                                                         &buffer_id);
+    if (!error) {
+        ofputil_packet_in_private_format(string, &pin, total_len, buffer_id,
+                                         port_map, table_map, verbosity);
+        ofputil_packet_in_private_destroy(&pin);
     }
-
-    ofputil_packet_in_private_destroy(&pin);
-
-    return 0;
+    return error;
 }
 
 static enum ofperr
@@ -264,56 +147,14 @@ ofp_print_packet_out(struct ds *string, const struct ofp_header *oh,
 
     ofpbuf_init(&ofpacts, 64);
     error = ofputil_decode_packet_out(&po, oh, NULL, &ofpacts);
-    if (error) {
-        ofpbuf_uninit(&ofpacts);
-        return error;
-    }
-
-    ds_put_char(string, ' ');
-    match_format(&po.flow_metadata, port_map, string, OFP_DEFAULT_PRIORITY);
-
-    ds_put_cstr(string, " actions=");
-    struct ofpact_format_params fp = {
-        .port_map = port_map,
-        .table_map = table_map,
-        .s = string,
-    };
-    ofpacts_format(po.ofpacts, po.ofpacts_len, &fp);
-
-    if (po.buffer_id == UINT32_MAX) {
-        ds_put_format(string, " data_len=%"PRIuSIZE, po.packet_len);
-        if (verbosity > 0 && po.packet_len > 0) {
-            ovs_be32 po_packet_type = po.flow_metadata.flow.packet_type;
-            char *packet = ofp_packet_to_string(po.packet, po.packet_len,
-                                                po_packet_type);
-            ds_put_char(string, '\n');
-            ds_put_cstr(string, packet);
-            free(packet);
-        }
-        if (verbosity > 2) {
-            ds_put_hex_dump(string, po.packet, po.packet_len, 0, false);
-        }
-    } else {
-        ds_put_format(string, " buffer=0x%08"PRIx32, po.buffer_id);
+    if (!error) {
+        ofputil_packet_out_format(string, &po, port_map, table_map, verbosity);
     }
-
     ofpbuf_uninit(&ofpacts);
-    return 0;
-}
-
-/* qsort comparison function. */
-static int
-compare_ports(const void *a_, const void *b_)
-{
-    const struct ofputil_phy_port *a = a_;
-    const struct ofputil_phy_port *b = b_;
-    uint16_t ap = ofp_to_u16(a->port_no);
-    uint16_t bp = ofp_to_u16(b->port_no);
-
-    return ap < bp ? -1 : ap > bp;
+    return error;
 }
 
-static void
+void
 ofp_print_bit_names(struct ds *string, uint32_t bits,
                     const char *(*bit_to_name)(uint32_t bit),
                     char separator)
@@ -349,280 +190,17 @@ ofp_print_bit_names(struct ds *string, uint32_t bits,
     }
 }
 
-static const char *
-netdev_feature_to_name(uint32_t bit)
-{
-    enum netdev_features f = bit;
-
-    switch (f) {
-    case NETDEV_F_10MB_HD:    return "10MB-HD";
-    case NETDEV_F_10MB_FD:    return "10MB-FD";
-    case NETDEV_F_100MB_HD:   return "100MB-HD";
-    case NETDEV_F_100MB_FD:   return "100MB-FD";
-    case NETDEV_F_1GB_HD:     return "1GB-HD";
-    case NETDEV_F_1GB_FD:     return "1GB-FD";
-    case NETDEV_F_10GB_FD:    return "10GB-FD";
-    case NETDEV_F_40GB_FD:    return "40GB-FD";
-    case NETDEV_F_100GB_FD:   return "100GB-FD";
-    case NETDEV_F_1TB_FD:     return "1TB-FD";
-    case NETDEV_F_OTHER:      return "OTHER";
-    case NETDEV_F_COPPER:     return "COPPER";
-    case NETDEV_F_FIBER:      return "FIBER";
-    case NETDEV_F_AUTONEG:    return "AUTO_NEG";
-    case NETDEV_F_PAUSE:      return "AUTO_PAUSE";
-    case NETDEV_F_PAUSE_ASYM: return "AUTO_PAUSE_ASYM";
-    }
-
-    return NULL;
-}
-
-static void
-ofp_print_port_features(struct ds *string, enum netdev_features features)
-{
-    ofp_print_bit_names(string, features, netdev_feature_to_name, ' ');
-    ds_put_char(string, '\n');
-}
-
-static const char *
-ofputil_port_config_to_name(uint32_t bit)
-{
-    enum ofputil_port_config pc = bit;
-
-    switch (pc) {
-    case OFPUTIL_PC_PORT_DOWN:    return "PORT_DOWN";
-    case OFPUTIL_PC_NO_STP:       return "NO_STP";
-    case OFPUTIL_PC_NO_RECV:      return "NO_RECV";
-    case OFPUTIL_PC_NO_RECV_STP:  return "NO_RECV_STP";
-    case OFPUTIL_PC_NO_FLOOD:     return "NO_FLOOD";
-    case OFPUTIL_PC_NO_FWD:       return "NO_FWD";
-    case OFPUTIL_PC_NO_PACKET_IN: return "NO_PACKET_IN";
-    }
-
-    return NULL;
-}
-
-static void
-ofp_print_port_config(struct ds *string, enum ofputil_port_config config)
-{
-    ofp_print_bit_names(string, config, ofputil_port_config_to_name, ' ');
-    ds_put_char(string, '\n');
-}
-
-static const char *
-ofputil_port_state_to_name(uint32_t bit)
-{
-    enum ofputil_port_state ps = bit;
-
-    switch (ps) {
-    case OFPUTIL_PS_LINK_DOWN: return "LINK_DOWN";
-    case OFPUTIL_PS_BLOCKED:   return "BLOCKED";
-    case OFPUTIL_PS_LIVE:      return "LIVE";
-
-    case OFPUTIL_PS_STP_LISTEN:
-    case OFPUTIL_PS_STP_LEARN:
-    case OFPUTIL_PS_STP_FORWARD:
-    case OFPUTIL_PS_STP_BLOCK:
-        /* Handled elsewhere. */
-        return NULL;
-    }
-
-    return NULL;
-}
-
-static void
-ofp_print_port_state(struct ds *string, enum ofputil_port_state state)
-{
-    enum ofputil_port_state stp_state;
-
-    /* The STP state is a 2-bit field so it doesn't fit in with the bitmask
-     * pattern.  We have to special case it.
-     *
-     * OVS doesn't support STP, so this field will always be 0 if we are
-     * talking to OVS, so we'd always print STP_LISTEN in that case.
-     * Therefore, we don't print anything at all if the value is STP_LISTEN, to
-     * avoid confusing users. */
-    stp_state = state & OFPUTIL_PS_STP_MASK;
-    if (stp_state) {
-        ds_put_cstr(string,
-                    (stp_state == OFPUTIL_PS_STP_LEARN ? "STP_LEARN"
-                     : stp_state == OFPUTIL_PS_STP_FORWARD ? "STP_FORWARD"
-                     : "STP_BLOCK"));
-        state &= ~OFPUTIL_PS_STP_MASK;
-        if (state) {
-            ofp_print_bit_names(string, state, ofputil_port_state_to_name,
-                                ' ');
-        }
-    } else {
-        ofp_print_bit_names(string, state, ofputil_port_state_to_name, ' ');
-    }
-    ds_put_char(string, '\n');
-}
-
-static void
-ofp_print_phy_port(struct ds *string, const struct ofputil_phy_port *port)
-{
-    char name[sizeof port->name];
-    int j;
-
-    memcpy(name, port->name, sizeof name);
-    for (j = 0; j < sizeof name - 1; j++) {
-        if (!isprint((unsigned char) name[j])) {
-            break;
-        }
-    }
-    name[j] = '\0';
-
-    ds_put_char(string, ' ');
-    ofputil_format_port(port->port_no, NULL, string);
-    ds_put_format(string, "(%s): addr:"ETH_ADDR_FMT"\n",
-                  name, ETH_ADDR_ARGS(port->hw_addr));
-
-    if (!eth_addr64_is_zero(port->hw_addr64)) {
-        ds_put_format(string, "     addr64: "ETH_ADDR64_FMT"\n",
-                      ETH_ADDR64_ARGS(port->hw_addr64));
-    }
-
-    ds_put_cstr(string, "     config:     ");
-    ofp_print_port_config(string, port->config);
-
-    ds_put_cstr(string, "     state:      ");
-    ofp_print_port_state(string, port->state);
-
-    if (port->curr) {
-        ds_put_format(string, "     current:    ");
-        ofp_print_port_features(string, port->curr);
-    }
-    if (port->advertised) {
-        ds_put_format(string, "     advertised: ");
-        ofp_print_port_features(string, port->advertised);
-    }
-    if (port->supported) {
-        ds_put_format(string, "     supported:  ");
-        ofp_print_port_features(string, port->supported);
-    }
-    if (port->peer) {
-        ds_put_format(string, "     peer:       ");
-        ofp_print_port_features(string, port->peer);
-    }
-    ds_put_format(string, "     speed: %"PRIu32" Mbps now, "
-                  "%"PRIu32" Mbps max\n",
-                  port->curr_speed / UINT32_C(1000),
-                  port->max_speed / UINT32_C(1000));
-}
-
-/* Given a buffer 'b' that contains an array of OpenFlow ports of type
- * 'ofp_version', writes a detailed description of each port into
- * 'string'. */
-static enum ofperr
-ofp_print_phy_ports(struct ds *string, uint8_t ofp_version,
-                    struct ofpbuf *b)
-{
-    struct ofputil_phy_port *ports;
-    size_t allocated_ports, n_ports;
-    int retval;
-    size_t i;
-
-    ports = NULL;
-    allocated_ports = 0;
-    for (n_ports = 0; ; n_ports++) {
-        if (n_ports >= allocated_ports) {
-            ports = x2nrealloc(ports, &allocated_ports, sizeof *ports);
-        }
-
-        retval = ofputil_pull_phy_port(ofp_version, b, &ports[n_ports]);
-        if (retval) {
-            break;
-        }
-    }
-
-    qsort(ports, n_ports, sizeof *ports, compare_ports);
-    for (i = 0; i < n_ports; i++) {
-        ofp_print_phy_port(string, &ports[i]);
-    }
-    free(ports);
-
-    return retval != EOF ? retval : 0;
-}
-
-static const char *
-ofputil_capabilities_to_name(uint32_t bit)
-{
-    enum ofputil_capabilities capabilities = bit;
-
-    switch (capabilities) {
-    case OFPUTIL_C_FLOW_STATS:   return "FLOW_STATS";
-    case OFPUTIL_C_TABLE_STATS:  return "TABLE_STATS";
-    case OFPUTIL_C_PORT_STATS:   return "PORT_STATS";
-    case OFPUTIL_C_IP_REASM:     return "IP_REASM";
-    case OFPUTIL_C_QUEUE_STATS:  return "QUEUE_STATS";
-    case OFPUTIL_C_ARP_MATCH_IP: return "ARP_MATCH_IP";
-    case OFPUTIL_C_STP:          return "STP";
-    case OFPUTIL_C_GROUP_STATS:  return "GROUP_STATS";
-    case OFPUTIL_C_PORT_BLOCKED: return "PORT_BLOCKED";
-    case OFPUTIL_C_BUNDLES:      return "BUNDLES";
-    case OFPUTIL_C_FLOW_MONITORING: return "FLOW_MONITORING";
-    }
-
-    return NULL;
-}
-
 static enum ofperr
 ofp_print_switch_features(struct ds *string, const struct ofp_header *oh)
 {
     struct ofputil_switch_features features;
     struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length));
     enum ofperr error = ofputil_pull_switch_features(&b, &features);
-    if (error) {
-        return error;
-    }
-
-    ds_put_format(string, " dpid:%016"PRIx64"\n", features.datapath_id);
-
-    ds_put_format(string, "n_tables:%"PRIu8", n_buffers:%"PRIu32,
-                  features.n_tables, features.n_buffers);
-    if (features.auxiliary_id) {
-        ds_put_format(string, ", auxiliary_id:%"PRIu8, features.auxiliary_id);
-    }
-    ds_put_char(string, '\n');
-
-    ds_put_cstr(string, "capabilities: ");
-    ofp_print_bit_names(string, features.capabilities,
-                        ofputil_capabilities_to_name, ' ');
-    ds_put_char(string, '\n');
-
-    switch ((enum ofp_version)oh->version) {
-    case OFP10_VERSION:
-        ds_put_cstr(string, "actions: ");
-        ofpact_bitmap_format(features.ofpacts, string);
-        ds_put_char(string, '\n');
-        break;
-    case OFP11_VERSION:
-    case OFP12_VERSION:
-        break;
-    case OFP13_VERSION:
-    case OFP14_VERSION:
-    case OFP15_VERSION:
-    case OFP16_VERSION:
-        return 0; /* no ports in ofp13_switch_features */
-    default:
-        OVS_NOT_REACHED();
-    }
-
-    return ofp_print_phy_ports(string, oh->version, &b);
-}
-
-static void
-ofp_print_switch_config(struct ds *string,
-                        const struct ofputil_switch_config *config)
-{
-    ds_put_format(string, " frags=%s",
-                  ofputil_frag_handling_to_string(config->frag));
-
-    if (config->invalid_ttl_to_controller > 0) {
-        ds_put_format(string, " invalid_ttl_to_controller");
+    if (!error) {
+        ofputil_switch_features_format(string, &features);
+        error = ofputil_phy_ports_format(string, oh->version, &b);
     }
-
-    ds_put_format(string, " miss_send_len=%"PRIu16"\n", config->miss_send_len);
+    return error;
 }
 
 static enum ofperr
@@ -635,7 +213,7 @@ ofp_print_set_config(struct ds *string, const struct ofp_header *oh)
     if (error) {
         return error;
     }
-    ofp_print_switch_config(string, &config);
+    ofputil_switch_config_format(string, &config);
     return 0;
 }
 
@@ -644,319 +222,34 @@ ofp_print_get_config_reply(struct ds *string, const struct ofp_header *oh)
 {
     struct ofputil_switch_config config;
     ofputil_decode_get_config_reply(oh, &config);
-    ofp_print_switch_config(string, &config);
+    ofputil_switch_config_format(string, &config);
     return 0;
 }
 
-static void print_wild(struct ds *string, const char *leader, int is_wild,
-            int verbosity, const char *format, ...)
-            OVS_PRINTF_FORMAT(5, 6);
-
-static void print_wild(struct ds *string, const char *leader, int is_wild,
-                       int verbosity, const char *format, ...)
-{
-    if (is_wild && verbosity < 2) {
-        return;
-    }
-    ds_put_cstr(string, leader);
-    if (!is_wild) {
-        va_list args;
-
-        va_start(args, format);
-        ds_put_format_valist(string, format, args);
-        va_end(args);
-    } else {
-        ds_put_char(string, '*');
-    }
-    ds_put_char(string, ',');
-}
-
-static void
-print_wild_port(struct ds *string, const char *leader, int is_wild,
-                int verbosity, ofp_port_t port,
-                const struct ofputil_port_map *port_map)
-{
-    if (is_wild && verbosity < 2) {
-        return;
-    }
-    ds_put_cstr(string, leader);
-    if (!is_wild) {
-        ofputil_format_port(port, port_map, string);
-    } else {
-        ds_put_char(string, '*');
-    }
-    ds_put_char(string, ',');
-}
-
-static void
-print_ip_netmask(struct ds *string, const char *leader, ovs_be32 ip,
-                 uint32_t wild_bits, int verbosity)
-{
-    if (wild_bits >= 32 && verbosity < 2) {
-        return;
-    }
-    ds_put_cstr(string, leader);
-    if (wild_bits < 32) {
-        ds_put_format(string, IP_FMT, IP_ARGS(ip));
-        if (wild_bits) {
-            ds_put_format(string, "/%d", 32 - wild_bits);
-        }
-    } else {
-        ds_put_char(string, '*');
-    }
-    ds_put_char(string, ',');
-}
-
-void
-ofp10_match_print(struct ds *f, const struct ofp10_match *om,
-                  const struct ofputil_port_map *port_map, int verbosity)
-{
-    char *s = ofp10_match_to_string(om, port_map, verbosity);
-    ds_put_cstr(f, s);
-    free(s);
-}
-
-char *
-ofp10_match_to_string(const struct ofp10_match *om,
-                      const struct ofputil_port_map *port_map, int verbosity)
-{
-    struct ds f = DS_EMPTY_INITIALIZER;
-    uint32_t w = ntohl(om->wildcards);
-    bool skip_type = false;
-    bool skip_proto = false;
-
-    if (!(w & OFPFW10_DL_TYPE)) {
-        skip_type = true;
-        if (om->dl_type == htons(ETH_TYPE_IP)) {
-            if (!(w & OFPFW10_NW_PROTO)) {
-                skip_proto = true;
-                if (om->nw_proto == IPPROTO_ICMP) {
-                    ds_put_cstr(&f, "icmp,");
-                } else if (om->nw_proto == IPPROTO_TCP) {
-                    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;
-                }
-            } else {
-                ds_put_cstr(&f, "ip,");
-            }
-        } else if (om->dl_type == htons(ETH_TYPE_ARP)) {
-            ds_put_cstr(&f, "arp,");
-        } else if (om->dl_type == htons(ETH_TYPE_RARP)){
-            ds_put_cstr(&f, "rarp,");
-        } else if (om->dl_type == htons(ETH_TYPE_MPLS)) {
-            ds_put_cstr(&f, "mpls,");
-        } else if (om->dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
-            ds_put_cstr(&f, "mplsm,");
-        } else {
-            skip_type = false;
-        }
-    }
-    print_wild_port(&f, "in_port=", w & OFPFW10_IN_PORT, verbosity,
-                    u16_to_ofp(ntohs(om->in_port)), port_map);
-    print_wild(&f, "dl_vlan=", w & OFPFW10_DL_VLAN, verbosity,
-               "%d", ntohs(om->dl_vlan));
-    print_wild(&f, "dl_vlan_pcp=", w & OFPFW10_DL_VLAN_PCP, verbosity,
-               "%d", om->dl_vlan_pcp);
-    print_wild(&f, "dl_src=", w & OFPFW10_DL_SRC, verbosity,
-               ETH_ADDR_FMT, ETH_ADDR_ARGS(om->dl_src));
-    print_wild(&f, "dl_dst=", w & OFPFW10_DL_DST, verbosity,
-               ETH_ADDR_FMT, ETH_ADDR_ARGS(om->dl_dst));
-    if (!skip_type) {
-        print_wild(&f, "dl_type=", w & OFPFW10_DL_TYPE, verbosity,
-                   "0x%04x", ntohs(om->dl_type));
-    }
-    print_ip_netmask(&f, "nw_src=", om->nw_src,
-                     (w & OFPFW10_NW_SRC_MASK) >> OFPFW10_NW_SRC_SHIFT,
-                     verbosity);
-    print_ip_netmask(&f, "nw_dst=", om->nw_dst,
-                     (w & OFPFW10_NW_DST_MASK) >> OFPFW10_NW_DST_SHIFT,
-                     verbosity);
-    if (!skip_proto) {
-        if (om->dl_type == htons(ETH_TYPE_ARP) ||
-            om->dl_type == htons(ETH_TYPE_RARP)) {
-            print_wild(&f, "arp_op=", w & OFPFW10_NW_PROTO, verbosity,
-                       "%u", om->nw_proto);
-        } else {
-            print_wild(&f, "nw_proto=", w & OFPFW10_NW_PROTO, verbosity,
-                       "%u", om->nw_proto);
-        }
-    }
-    print_wild(&f, "nw_tos=", w & OFPFW10_NW_TOS, verbosity,
-               "%u", om->nw_tos);
-    if (om->nw_proto == IPPROTO_ICMP) {
-        print_wild(&f, "icmp_type=", w & OFPFW10_ICMP_TYPE, verbosity,
-                   "%d", ntohs(om->tp_src));
-        print_wild(&f, "icmp_code=", w & OFPFW10_ICMP_CODE, verbosity,
-                   "%d", ntohs(om->tp_dst));
-    } else {
-        print_wild(&f, "tp_src=", w & OFPFW10_TP_SRC, verbosity,
-                   "%d", ntohs(om->tp_src));
-        print_wild(&f, "tp_dst=", w & OFPFW10_TP_DST, verbosity,
-                   "%d", ntohs(om->tp_dst));
-    }
-    ds_chomp(&f, ',');
-    return ds_cstr(&f);
-}
-
-static void
-ofp_print_flow_flags(struct ds *s, enum ofputil_flow_mod_flags flags)
-{
-    if (flags & OFPUTIL_FF_SEND_FLOW_REM) {
-        ds_put_cstr(s, "send_flow_rem ");
-    }
-    if (flags & OFPUTIL_FF_CHECK_OVERLAP) {
-        ds_put_cstr(s, "check_overlap ");
-    }
-    if (flags & OFPUTIL_FF_RESET_COUNTS) {
-        ds_put_cstr(s, "reset_counts ");
-    }
-    if (flags & OFPUTIL_FF_NO_PKT_COUNTS) {
-        ds_put_cstr(s, "no_packet_counts ");
-    }
-    if (flags & OFPUTIL_FF_NO_BYT_COUNTS) {
-        ds_put_cstr(s, "no_byte_counts ");
-    }
-    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 ");
-    }
-}
-
 static enum ofperr
-ofp_print_flow_mod(struct ds *s, const struct ofp_header *oh,
-                   const struct ofputil_port_map *port_map,
-                   const struct ofputil_table_map *table_map, int verbosity)
+ofp_print_table_features_reply(struct ds *s, const struct ofp_header *oh,
+                               const struct ofputil_table_map *table_map)
 {
-    struct ofputil_flow_mod fm;
-    struct ofpbuf ofpacts;
-    bool need_priority;
-    enum ofperr error;
-    enum ofpraw raw;
-    enum ofputil_protocol protocol;
-
-    protocol = ofputil_protocol_from_ofp_version(oh->version);
-    protocol = ofputil_protocol_set_tid(protocol, true);
-
-    ofpbuf_init(&ofpacts, 64);
-    error = ofputil_decode_flow_mod(&fm, oh, protocol, NULL, NULL, &ofpacts,
-                                    OFPP_MAX, 255);
-    if (error) {
-        ofpbuf_uninit(&ofpacts);
-        return error;
-    }
-
-    ds_put_char(s, ' ');
-    switch (fm.command) {
-    case OFPFC_ADD:
-        ds_put_cstr(s, "ADD");
-        break;
-    case OFPFC_MODIFY:
-        ds_put_cstr(s, "MOD");
-        break;
-    case OFPFC_MODIFY_STRICT:
-        ds_put_cstr(s, "MOD_STRICT");
-        break;
-    case OFPFC_DELETE:
-        ds_put_cstr(s, "DEL");
-        break;
-    case OFPFC_DELETE_STRICT:
-        ds_put_cstr(s, "DEL_STRICT");
-        break;
-    default:
-        ds_put_format(s, "cmd:%d", fm.command);
-    }
-    if (fm.table_id != 0
-        || ofputil_table_map_get_name(table_map, fm.table_id)) {
-        ds_put_format(s, " table:");
-        ofputil_format_table(fm.table_id, table_map, s);
-    }
-
-    ds_put_char(s, ' ');
-    ofpraw_decode(&raw, oh);
-    if (verbosity >= 3 && raw == OFPRAW_OFPT10_FLOW_MOD) {
-        const struct ofp10_flow_mod *ofm = ofpmsg_body(oh);
-        ofp10_match_print(s, &ofm->match, port_map, verbosity);
-
-        /* ofp_print_match() doesn't print priority. */
-        need_priority = true;
-    } else if (verbosity >= 3 && raw == OFPRAW_NXT_FLOW_MOD) {
-        const struct nx_flow_mod *nfm = ofpmsg_body(oh);
-        const void *nxm = nfm + 1;
-        char *nxm_s;
-
-        nxm_s = nx_match_to_string(nxm, ntohs(nfm->match_len));
-        ds_put_cstr(s, nxm_s);
-        free(nxm_s);
-
-        /* nx_match_to_string() doesn't print priority. */
-        need_priority = true;
-    } else {
-        match_format(&fm.match, port_map, s, fm.priority);
+    struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length));
 
-        /* match_format() does print priority. */
-        need_priority = false;
-    }
+    struct ofputil_table_features prev;
+    for (int i = 0; ; i++) {
+        struct ofputil_table_features tf;
+        int retval;
 
-    if (ds_last(s) != ' ') {
-        ds_put_char(s, ' ');
-    }
-    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)) {
-        ds_put_format(s, "cookie:0x%"PRIx64"/0x%"PRIx64" ",
-                ntohll(fm.cookie), ntohll(fm.cookie_mask));
-    }
-    if (fm.idle_timeout != OFP_FLOW_PERMANENT) {
-        ds_put_format(s, "idle:%"PRIu16" ", fm.idle_timeout);
-    }
-    if (fm.hard_timeout != OFP_FLOW_PERMANENT) {
-        ds_put_format(s, "hard:%"PRIu16" ", fm.hard_timeout);
-    }
-    if (fm.importance != 0) {
-        ds_put_format(s, "importance:%"PRIu16" ", fm.importance);
-    }
-    if (fm.priority != OFP_DEFAULT_PRIORITY && need_priority) {
-        ds_put_format(s, "pri:%d ", fm.priority);
-    }
-    if (fm.buffer_id != UINT32_MAX) {
-        ds_put_format(s, "buf:0x%"PRIx32" ", fm.buffer_id);
-    }
-    if (fm.out_port != OFPP_ANY) {
-        ds_put_format(s, "out_port:");
-        ofputil_format_port(fm.out_port, port_map, s);
-        ds_put_char(s, ' ');
-    }
+        retval = ofputil_decode_table_features(&b, &tf, true);
+        if (retval) {
+            return retval != EOF ? retval : 0;
+        }
 
-    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;
+        ds_put_char(s, '\n');
+        ofputil_table_features_format(s, &tf, i ? &prev : NULL, NULL, NULL,
+                                      table_map);
+        prev = tf;
     }
-    ofp_print_flow_flags(s, fm.flags);
-
-    ds_put_cstr(s, "actions=");
-    struct ofpact_format_params fp = {
-        .port_map = port_map,
-        .table_map = table_map,
-        .s = s,
-    };
-    ofpacts_format(fm.ofpacts, fm.ofpacts_len, &fp);
-    ofpbuf_uninit(&ofpacts);
-
-    return 0;
 }
 
-static void
+void
 ofp_print_duration(struct ds *string, unsigned int sec, unsigned int nsec)
 {
     ds_put_format(string, "%u", sec);
@@ -967,261 +260,59 @@ ofp_print_duration(struct ds *string, unsigned int sec, unsigned int nsec)
      * 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) {
-        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');
-}
-
-/* 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_FLOW_REMOVED_REASON_BUFSIZE. */
-#define OFP_FLOW_REMOVED_REASON_BUFSIZE (INT_STRLEN(int) + 1)
-static const char *
-ofp_flow_removed_reason_to_string(enum ofp_flow_removed_reason reason,
-                                  char *reasonbuf, size_t bufsize)
-{
-    switch (reason) {
-    case OFPRR_IDLE_TIMEOUT:
-        return "idle";
-    case OFPRR_HARD_TIMEOUT:
-        return "hard";
-    case OFPRR_DELETE:
-        return "delete";
-    case OFPRR_GROUP_DELETE:
-        return "group_delete";
-    case OFPRR_EVICTION:
-        return "eviction";
-    case OFPRR_METER_DELETE:
-        return "meter_delete";
-    case OVS_OFPRR_NONE:
-    default:
-        snprintf(reasonbuf, bufsize, "%d", (int) reason);
-        return reasonbuf;
-    }
-}
-
-static enum ofperr
-ofp_print_flow_removed(struct ds *string, const struct ofp_header *oh,
-                       const struct ofputil_port_map *port_map,
-                       const struct ofputil_table_map *table_map)
-{
-    char reasonbuf[OFP_FLOW_REMOVED_REASON_BUFSIZE];
-    struct ofputil_flow_removed fr;
-    enum ofperr error;
-
-    error = ofputil_decode_flow_removed(&fr, oh);
-    if (error) {
-        return error;
-    }
-
-    ds_put_char(string, ' ');
-    match_format(&fr.match, port_map, string, fr.priority);
-
-    ds_put_format(string, " reason=%s",
-                  ofp_flow_removed_reason_to_string(fr.reason, reasonbuf,
-                                                    sizeof reasonbuf));
-
-    if (fr.table_id != 255) {
-        ds_put_format(string, " table_id=");
-        ofputil_format_table(fr.table_id, table_map, string);
-    }
-
-    if (fr.cookie != htonll(0)) {
-        ds_put_format(string, " cookie:0x%"PRIx64, ntohll(fr.cookie));
-    }
-    ds_put_cstr(string, " duration");
-    ofp_print_duration(string, fr.duration_sec, fr.duration_nsec);
-    ds_put_format(string, " idle%"PRIu16, fr.idle_timeout);
-    if (fr.hard_timeout) {
-        /* The hard timeout was only added in OF1.2, so only print it if it is
-         * actually in use to avoid gratuitous change to the formatting. */
-        ds_put_format(string, " hard%"PRIu16, fr.hard_timeout);
-    }
-    ds_put_format(string, " pkts%"PRIu64" bytes%"PRIu64"\n",
-                  fr.packet_count, fr.byte_count);
-    return 0;
-}
-
-static enum ofperr
-ofp_print_port_mod(struct ds *string, const struct ofp_header *oh,
-                   const struct ofputil_port_map *port_map)
-{
-    struct ofputil_port_mod pm;
-    enum ofperr error;
-
-    error = ofputil_decode_port_mod(oh, &pm, true);
-    if (error) {
-        return error;
-    }
-
-    ds_put_cstr(string, " port: ");
-    ofputil_format_port(pm.port_no, port_map, string);
-    ds_put_format(string, ": addr:"ETH_ADDR_FMT"\n",
-                  ETH_ADDR_ARGS(pm.hw_addr));
-    if (!eth_addr64_is_zero(pm.hw_addr64)) {
-        ds_put_format(string, "     addr64: "ETH_ADDR64_FMT"\n",
-                      ETH_ADDR64_ARGS(pm.hw_addr64));
-    }
-
-    ds_put_cstr(string, "     config: ");
-    ofp_print_port_config(string, pm.config);
-
-    ds_put_cstr(string, "     mask:   ");
-    ofp_print_port_config(string, pm.mask);
-
-    ds_put_cstr(string, "     advertise: ");
-    if (pm.advertise) {
-        ofp_print_port_features(string, pm.advertise);
-    } else {
-        ds_put_cstr(string, "UNCHANGED\n");
-    }
-
-    return 0;
-}
-
-static const char *
-ofputil_table_miss_to_string(enum ofputil_table_miss miss)
-{
-    switch (miss) {
-    case OFPUTIL_TABLE_MISS_DEFAULT: return "default";
-    case OFPUTIL_TABLE_MISS_CONTROLLER: return "controller";
-    case OFPUTIL_TABLE_MISS_CONTINUE: return "continue";
-    case OFPUTIL_TABLE_MISS_DROP: return "drop";
-    default: return "***error***";
-    }
-}
-
-static const char *
-ofputil_table_eviction_to_string(enum ofputil_table_eviction eviction)
-{
-    switch (eviction) {
-    case OFPUTIL_TABLE_EVICTION_DEFAULT: return "default";
-    case OFPUTIL_TABLE_EVICTION_ON: return "on";
-    case OFPUTIL_TABLE_EVICTION_OFF: return "off";
-    default: return "***error***";
-    }
-
-}
-
-static const char *
-ofputil_eviction_flag_to_string(uint32_t bit)
-{
-    enum ofp14_table_mod_prop_eviction_flag eviction_flag = bit;
-
-    switch (eviction_flag) {
-    case OFPTMPEF14_OTHER:      return "OTHER";
-    case OFPTMPEF14_IMPORTANCE: return "IMPORTANCE";
-    case OFPTMPEF14_LIFETIME:   return "LIFETIME";
+     *
+     * If the fractional seconds are more precise than milliseconds, print the
+     * number of decimals needed to express them exactly.
+     */
+    if (nsec > 0) {
+        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--;
+            }
+        }
     }
-
-    return NULL;
+    ds_put_char(string, 's');
 }
 
-/* Appends to 'string' a description of the bitmap of OFPTMPEF14_* values in
- * 'eviction_flags'. */
-static void
-ofputil_put_eviction_flags(struct ds *string, uint32_t eviction_flags)
+static enum ofperr
+ofp_print_flow_removed(struct ds *string, const struct ofp_header *oh,
+                       const struct ofputil_port_map *port_map,
+                       const struct ofputil_table_map *table_map)
 {
-    if (eviction_flags != UINT32_MAX) {
-        ofp_print_bit_names(string, eviction_flags,
-                            ofputil_eviction_flag_to_string, '|');
-    } else {
-        ds_put_cstr(string, "(default)");
+    struct ofputil_flow_removed fr;
+    enum ofperr error = ofputil_decode_flow_removed(&fr, oh);
+    if (!error) {
+        ofputil_flow_removed_format(string, &fr, port_map, table_map);
     }
+    return error;
 }
 
-static const char *
-ofputil_table_vacancy_to_string(enum ofputil_table_vacancy vacancy)
+static enum ofperr
+ofp_print_port_mod(struct ds *string, const struct ofp_header *oh,
+                   const struct ofputil_port_map *port_map)
 {
-    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***";
+    struct ofputil_port_mod pm;
+    enum ofperr error = ofputil_decode_port_mod(oh, &pm, true);
+    if (!error) {
+        ofputil_port_mod_format(string, &pm, port_map);
     }
-
+    return error;
 }
 
 static enum ofperr
 ofp_print_table_mod(struct ds *string, const struct ofp_header *oh,
                   const struct ofputil_table_map *table_map)
 {
-    struct ofputil_table_mod pm;
-    enum ofperr error;
-
-    error = ofputil_decode_table_mod(oh, &pm);
-    if (error) {
-        return error;
-    }
-
-    if (pm.table_id == 0xff) {
-        ds_put_cstr(string, " table_id: ALL_TABLES");
-    } else {
-        ds_put_format(string, " table_id=");
-        ofputil_format_table(pm.table_id, table_map, string);
-    }
-
-    if (pm.miss != OFPUTIL_TABLE_MISS_DEFAULT) {
-        ds_put_format(string, ", flow_miss_config=%s",
-                      ofputil_table_miss_to_string(pm.miss));
-    }
-    if (pm.eviction != OFPUTIL_TABLE_EVICTION_DEFAULT) {
-        ds_put_format(string, ", eviction=%s",
-                      ofputil_table_eviction_to_string(pm.eviction));
-    }
-    if (pm.eviction_flags != UINT32_MAX) {
-        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);
-        }
-    }
-
-    return 0;
-}
-
-/* This function will print the Table description properties. */
-static void
-ofp_print_table_desc(struct ds *string, const struct ofputil_table_desc *td,
-                     const struct ofputil_table_map *table_map)
-{
-    ds_put_format(string, "\n  table ");
-    ofputil_format_table(td->table_id, table_map, string);
-    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');
-    ds_put_format(string, "   vacancy=%s",
-                  ofputil_table_vacancy_to_string(td->vacancy));
-    if (td->vacancy == OFPUTIL_TABLE_VACANCY_ON) {
-        ds_put_format(string, " vacancy_down=%"PRIu8"%%",
-                      td->table_vacancy.vacancy_down);
-        ds_put_format(string, " vacancy_up=%"PRIu8"%%",
-                      td->table_vacancy.vacancy_up);
-        ds_put_format(string, " vacancy=%"PRIu8"%%",
-                      td->table_vacancy.vacancy);
+    struct ofputil_table_mod tm;
+    enum ofperr error = ofputil_decode_table_mod(oh, &tm);
+    if (!error) {
+        ofputil_table_mod_format(string, &tm, table_map);
     }
-    ds_put_char(string, '\n');
+    return error;
 }
 
 static enum ofperr
@@ -1243,7 +334,7 @@ ofp_print_table_status_message(struct ds *string, const struct ofp_header *oh,
     }
 
     ds_put_format(string, "\ntable_desc:-");
-    ofp_print_table_desc(string, &ts.desc, table_map);
+    ofputil_table_desc_format(string, &ts.desc, table_map);
 
     return 0;
 }
@@ -1616,19 +707,7 @@ ofp_print_error(struct ds *string, enum ofperr error)
 static enum ofperr
 ofp_print_hello(struct ds *string, const struct ofp_header *oh)
 {
-    uint32_t allowed_versions;
-    bool ok;
-
-    ok = ofputil_decode_hello(oh, &allowed_versions);
-
-    ds_put_cstr(string, "\n version bitmap: ");
-    ofputil_format_version_bitmap(string, allowed_versions);
-
-    if (!ok) {
-        ds_put_cstr(string, "\n unknown data in hello:\n");
-        ds_put_hex_dump(string, oh, ntohs(oh->length), 0, true);
-    }
-
+    ofputil_hello_format(string, oh);
     return 0;
 }
 
@@ -1638,23 +717,11 @@ ofp_print_error_msg(struct ds *string, const struct ofp_header *oh,
                     const struct ofputil_table_map *table_map)
 {
     struct ofpbuf payload;
-    enum ofperr error;
-    char *s;
-
-    error = ofperr_decode_msg(oh, &payload);
+    enum ofperr error = ofperr_decode_msg(oh, &payload);
     if (!error) {
         return OFPERR_OFPBRC_BAD_LEN;
     }
-
-    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);
-    } else {
-        s = ofp_to_string(payload.data, payload.size, port_map, table_map, 1);
-        ds_put_cstr(string, s);
-        free(s);
-    }
+    ofperr_msg_format(string, error, &payload, port_map, table_map);
     ofpbuf_uninit(&payload);
 
     return 0;
@@ -1664,23 +731,11 @@ static enum ofperr
 ofp_print_port_status(struct ds *string, const struct ofp_header *oh)
 {
     struct ofputil_port_status ps;
-    enum ofperr error;
-
-    error = ofputil_decode_port_status(oh, &ps);
-    if (error) {
-        return error;
-    }
-
-    if (ps.reason == OFPPR_ADD) {
-        ds_put_format(string, " ADD:");
-    } else if (ps.reason == OFPPR_DELETE) {
-        ds_put_format(string, " DEL:");
-    } else if (ps.reason == OFPPR_MODIFY) {
-        ds_put_format(string, " MOD:");
+    enum ofperr error = ofputil_decode_port_status(oh, &ps);
+    if (!error) {
+        ofputil_port_status_format(string, &ps);
     }
-
-    ofp_print_phy_port(string, &ps.desc);
-    return 0;
+    return error;
 }
 
 static enum ofperr
@@ -1709,100 +764,12 @@ ofp_print_flow_stats_request(struct ds *string, const struct ofp_header *oh,
                              const struct ofputil_table_map *table_map)
 {
     struct ofputil_flow_stats_request fsr;
-    enum ofperr error;
-
-    error = ofputil_decode_flow_stats_request(&fsr, oh, NULL, NULL);
-    if (error) {
-        return error;
-    }
-
-    if (fsr.table_id != 0xff) {
-        ds_put_format(string, " table=");
-        ofputil_format_table(fsr.table_id, table_map, string);
-    }
-
-    if (fsr.out_port != OFPP_ANY) {
-        ds_put_cstr(string, " out_port=");
-        ofputil_format_port(fsr.out_port, port_map, string);
-    }
-
-    ds_put_char(string, ' ');
-    match_format(&fsr.match, port_map, string, OFP_DEFAULT_PRIORITY);
-
-    return 0;
-}
-
-/* Appends a textual form of 'fs' to 'string', translating port numbers to
- * names using 'port_map' (if provided).  If 'show_stats' is true, the output
- * includes the flow duration, packet and byte counts, and its idle and hard
- * ages, otherwise they are omitted. */
-void
-ofp_print_flow_stats(struct ds *string, const struct ofputil_flow_stats *fs,
-                     const struct ofputil_port_map *port_map,
-                     const struct ofputil_table_map *table_map,
-                     bool show_stats)
-{
-    if (show_stats || fs->cookie) {
-        ds_put_format(string, "%scookie=%s0x%"PRIx64", ",
-                      colors.param, colors.end, ntohll(fs->cookie));
-    }
-    if (show_stats) {
-        ds_put_format(string, "%sduration=%s", colors.param, colors.end);
-        ofp_print_duration(string, fs->duration_sec, fs->duration_nsec);
-        ds_put_cstr(string, ", ");
-    }
-
-    if (show_stats || fs->table_id
-        || ofputil_table_map_get_name(table_map, fs->table_id) != NULL) {
-        ds_put_format(string, "%stable=%s", colors.special, colors.end);
-        ofputil_format_table(fs->table_id, table_map, string);
-        ds_put_cstr(string, ", ");
-    }
-    if (show_stats) {
-        ds_put_format(string, "%sn_packets=%s%"PRIu64", ",
-                      colors.param, colors.end, fs->packet_count);
-        ds_put_format(string, "%sn_bytes=%s%"PRIu64", ",
-                      colors.param, colors.end, fs->byte_count);
-    }
-    if (fs->idle_timeout != OFP_FLOW_PERMANENT) {
-        ds_put_format(string, "%sidle_timeout=%s%"PRIu16", ",
-                      colors.param, colors.end, fs->idle_timeout);
-    }
-    if (fs->hard_timeout != OFP_FLOW_PERMANENT) {
-        ds_put_format(string, "%shard_timeout=%s%"PRIu16", ",
-                      colors.param, colors.end, fs->hard_timeout);
-    }
-    if (fs->flags) {
-        ofp_print_flow_flags(string, fs->flags);
-    }
-    if (fs->importance != 0) {
-        ds_put_format(string, "%simportance=%s%"PRIu16", ",
-                      colors.param, colors.end, fs->importance);
-    }
-    if (show_stats && fs->idle_age >= 0) {
-        ds_put_format(string, "%sidle_age=%s%d, ",
-                      colors.param, colors.end, fs->idle_age);
-    }
-    if (show_stats && fs->hard_age >= 0 && fs->hard_age != fs->duration_sec) {
-        ds_put_format(string, "%shard_age=%s%d, ",
-                      colors.param, colors.end, fs->hard_age);
-    }
-
-    /* Print the match, followed by a space (but omit the space if the match
-     * was an empty string). */
-    size_t length = string->length;
-    match_format(&fs->match, port_map, string, fs->priority);
-    if (string->length != length) {
-        ds_put_char(string, ' ');
+    enum ofperr error = ofputil_decode_flow_stats_request(&fsr, oh, NULL,
+                                                          NULL);
+    if (!error) {
+        ofputil_flow_stats_request_format(string, &fsr, port_map, table_map);
     }
-
-    ds_put_format(string, "%sactions=%s", colors.actions, colors.end);
-    struct ofpact_format_params fp = {
-        .port_map = port_map,
-        .table_map = table_map,
-        .s = string,
-    };
-    ofpacts_format(fs->ofpacts, fs->ofpacts_len, &fp);
+    return error;
 }
 
 static enum ofperr
@@ -1823,7 +790,7 @@ ofp_print_flow_stats_reply(struct ds *string, const struct ofp_header *oh,
             break;
         }
         ds_put_cstr(string, "\n ");
-        ofp_print_flow_stats(string, &fs, port_map, table_map, true);
+        ofputil_flow_stats_format(string, &fs, port_map, table_map, true);
      }
     ofpbuf_uninit(&ofpacts);
 
@@ -1837,15 +804,10 @@ ofp_print_aggregate_stats_reply(struct ds *string, const struct ofp_header *oh)
     enum ofperr error;
 
     error = ofputil_decode_aggregate_stats_reply(&as, oh);
-    if (error) {
-        return error;
+    if (!error) {
+        ofputil_aggregate_stats_format(string, &as);
     }
-
-    ds_put_format(string, " packet_count=%"PRIu64, as.packet_count);
-    ds_put_format(string, " byte_count=%"PRIu64, as.byte_count);
-    ds_put_format(string, " flow_count=%"PRIu32, as.flow_count);
-
-    return 0;
+    return error;
 }
 
 static void
@@ -2047,10 +1009,10 @@ ofp_print_table_stats_reply(struct ds *string, const struct ofp_header *oh,
         }
 
         ds_put_char(string, '\n');
-        ofp_print_table_features(string,
-                                 &features, i ? &prev_features : NULL,
-                                 &stats, i ? &prev_stats : NULL,
-                                 table_map);
+        ofputil_table_features_format(string,
+                                      &features, i ? &prev_features : NULL,
+                                      &stats, i ? &prev_stats : NULL,
+                                      table_map);
         prev_features = features;
         prev_stats = stats;
     }
@@ -2153,7 +1115,7 @@ ofp_print_ofpst_port_desc_reply(struct ds *string,
     struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length));
     ofpraw_pull_assert(&b);
     ds_put_char(string, '\n');
-    return ofp_print_phy_ports(string, oh->version, &b);
+    return ofputil_phy_ports_format(string, oh->version, &b);
 }
 
 static void
@@ -2480,23 +1442,6 @@ ofp_print_nxt_flow_monitor_cancel(struct ds *string,
     return 0;
 }
 
-static const char *
-nx_flow_monitor_flags_to_name(uint32_t bit)
-{
-    enum nx_flow_monitor_flags fmf = bit;
-
-    switch (fmf) {
-    case NXFMF_INITIAL: return "initial";
-    case NXFMF_ADD: return "add";
-    case NXFMF_DELETE: return "delete";
-    case NXFMF_MODIFY: return "modify";
-    case NXFMF_ACTIONS: return "actions";
-    case NXFMF_OWN: return "own";
-    }
-
-    return NULL;
-}
-
 static enum ofperr
 ofp_print_nxst_flow_monitor_request(struct ds *string,
                                     const struct ofp_header *oh,
@@ -2513,23 +1458,8 @@ ofp_print_nxst_flow_monitor_request(struct ds *string,
             return retval != EOF ? retval : 0;
         }
 
-        ds_put_format(string, "\n id=%"PRIu32" flags=", request.id);
-        ofp_print_bit_names(string, request.flags,
-                            nx_flow_monitor_flags_to_name, ',');
-
-        if (request.out_port != OFPP_NONE) {
-            ds_put_cstr(string, " out_port=");
-            ofputil_format_port(request.out_port, port_map, string);
-        }
-
-        if (request.table_id != 0xff) {
-            ds_put_format(string, " table=");
-            ofputil_format_table(request.table_id, table_map, string);
-        }
-
-        ds_put_char(string, ' ');
-        match_format(&request.match, port_map, string, OFP_DEFAULT_PRIORITY);
-        ds_chomp(string, ' ');
+        ofputil_flow_monitor_request_format(string, &request,
+                                            port_map, table_map);
     }
 }
 
@@ -2544,65 +1474,13 @@ ofp_print_nxst_flow_monitor_reply(struct ds *string,
     struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length));
 
     for (;;) {
-        char reasonbuf[OFP_FLOW_REMOVED_REASON_BUFSIZE];
         struct ofputil_flow_update update;
-        int retval;
-
-        retval = ofputil_decode_flow_update(&update, &b, &ofpacts);
+        int retval = ofputil_decode_flow_update(&update, &b, &ofpacts);
         if (retval) {
             ofpbuf_uninit(&ofpacts);
             return retval != EOF ? retval : 0;
         }
-
-        ds_put_cstr(string, "\n event=");
-        switch (update.event) {
-        case NXFME_ADDED:
-            ds_put_cstr(string, "ADDED");
-            break;
-
-        case NXFME_DELETED:
-            ds_put_format(string, "DELETED reason=%s",
-                          ofp_flow_removed_reason_to_string(update.reason,
-                                                            reasonbuf,
-                                                            sizeof reasonbuf));
-            break;
-
-        case NXFME_MODIFIED:
-            ds_put_cstr(string, "MODIFIED");
-            break;
-
-        case NXFME_ABBREV:
-            ds_put_format(string, "ABBREV xid=0x%"PRIx32, ntohl(update.xid));
-            continue;
-        }
-
-        ds_put_format(string, " table=");
-        ofputil_format_table(update.table_id, table_map, string);
-        if (update.idle_timeout != OFP_FLOW_PERMANENT) {
-            ds_put_format(string, " idle_timeout=%"PRIu16,
-                          update.idle_timeout);
-        }
-        if (update.hard_timeout != OFP_FLOW_PERMANENT) {
-            ds_put_format(string, " hard_timeout=%"PRIu16,
-                          update.hard_timeout);
-        }
-        ds_put_format(string, " cookie=%#"PRIx64, ntohll(update.cookie));
-
-        ds_put_char(string, ' ');
-        match_format(&update.match, port_map, string, OFP_DEFAULT_PRIORITY);
-
-        if (update.ofpacts_len) {
-            if (string->string[string->length - 1] != ' ') {
-                ds_put_char(string, ' ');
-            }
-            ds_put_cstr(string, "actions=");
-            struct ofpact_format_params fp = {
-                .port_map = port_map,
-                .table_map = table_map,
-                .s = string,
-            };
-            ofpacts_format(update.ofpacts, update.ofpacts_len, &fp);
-        }
+        ofputil_flow_update_format(string, &update, port_map, table_map);
     }
 }
 
@@ -2947,294 +1825,6 @@ ofp_print_group_mod(struct ds *s, const struct ofp_header *oh,
     return 0;
 }
 
-static void
-print_table_action_features(struct ds *s,
-                            const struct ofputil_table_action_features *taf)
-{
-    if (taf->ofpacts) {
-        ds_put_cstr(s, "        actions: ");
-        ofpact_bitmap_format(taf->ofpacts, s);
-        ds_put_char(s, '\n');
-    }
-
-    if (!bitmap_is_all_zeros(taf->set_fields.bm, MFF_N_IDS)) {
-        int i;
-
-        ds_put_cstr(s, "        supported on Set-Field:");
-        BITMAP_FOR_EACH_1 (i, MFF_N_IDS, taf->set_fields.bm) {
-            ds_put_format(s, " %s", mf_from_id(i)->name);
-        }
-        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->ofpacts == b->ofpacts
-            && bitmap_equal(a->set_fields.bm, b->set_fields.bm, MFF_N_IDS));
-}
-
-static bool
-table_action_features_empty(const struct ofputil_table_action_features *taf)
-{
-    return !taf->ofpacts && bitmap_is_all_zeros(taf->set_fields.bm, MFF_N_IDS);
-}
-
-static void
-print_table_instruction_features(
-    struct ds *s,
-    const struct ofputil_table_instruction_features *tif,
-    const struct ofputil_table_instruction_features *prev_tif)
-{
-    int start, end;
-
-    if (!bitmap_is_all_zeros(tif->next, 255)) {
-        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');
-    }
-
-    if (tif->instructions) {
-        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)) {
-                    const char *name = ovs_instruction_name_from_type(i);
-                    if (name) {
-                        ds_put_cstr(s, name);
-                    } else {
-                        ds_put_format(s, "%d", i);
-                    }
-                    ds_put_char(s, ',');
-                }
-            }
-            ds_chomp(s, ',');
-            ds_put_char(s, '\n');
-        }
-    }
-
-    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");
-        print_table_action_features(s, &tif->apply);
-    } else if (tif->write.ofpacts
-               || !bitmap_is_all_zeros(tif->write.set_fields.bm, MFF_N_IDS)) {
-        ds_put_cstr(s, "      Write-Actions and Apply-Actions features:\n");
-        print_table_action_features(s, &tif->write);
-    }
-}
-
-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 bool
-table_instruction_features_empty(
-    const struct ofputil_table_instruction_features *tif)
-{
-    return (bitmap_is_all_zeros(tif->next, 255)
-            && !tif->instructions
-            && table_action_features_empty(&tif->write)
-            && table_action_features_empty(&tif->apply));
-}
-
-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_features *prev_features,
-                         const struct ofputil_table_stats *stats,
-                         const struct ofputil_table_stats *prev_stats,
-                         const struct ofputil_table_map *table_map)
-{
-    int i;
-
-    ds_put_format(s, "  table ");
-    ofputil_format_table(features->table_id, table_map, s);
-    if (features->name[0]) {
-        ds_put_format(s, " (\"%s\")", features->name);
-    }
-    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 (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));
-    }
-
-    if (features->miss_config != OFPUTIL_TABLE_MISS_DEFAULT) {
-        ds_put_format(s, "    config=%s\n",
-                      ofputil_table_miss_to_string(features->miss_config));
-    }
-
-    if (features->supports_eviction >= 0) {
-        ds_put_format(s, "    eviction: %ssupported\n",
-                      features->supports_eviction ? "" : "not ");
-
-    }
-    if (features->supports_vacancy_events >= 0) {
-        ds_put_format(s, "    vacancy events: %ssupported\n",
-                      features->supports_vacancy_events ? "" : "not ");
-
-    }
-
-    if (features->max_entries) {
-        ds_put_format(s, "    max_entries=%"PRIu32"\n", features->max_entries);
-    }
-
-    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, prev_nonmiss);
-        ds_put_cstr(s, "    instructions (table miss):\n");
-        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, prev_nonmiss);
-    }
-
-    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"));
-            }
-        }
-    }
-}
-
-static enum ofperr
-ofp_print_table_features_reply(struct ds *s, const struct ofp_header *oh,
-                               const struct ofputil_table_map *table_map)
-{
-    struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length));
-
-    struct ofputil_table_features prev;
-    for (int i = 0; ; i++) {
-        struct ofputil_table_features tf;
-        int retval;
-
-        retval = ofputil_decode_table_features(&b, &tf, true);
-        if (retval) {
-            return retval != EOF ? retval : 0;
-        }
-
-        ds_put_char(s, '\n');
-        ofp_print_table_features(s, &tf, i ? &prev : NULL, NULL, NULL,
-                                 table_map);
-        prev = tf;
-    }
-}
-
 static enum ofperr
 ofp_print_table_desc_reply(struct ds *s, const struct ofp_header *oh,
                            const struct ofputil_table_map *table_map)
@@ -3248,7 +1838,7 @@ ofp_print_table_desc_reply(struct ds *s, const struct ofp_header *oh,
         if (retval) {
             return retval != EOF ? retval : 0;
         }
-        ofp_print_table_desc(s, &td, table_map);
+        ofputil_table_desc_format(s, &td, table_map);
     }
 }
 
@@ -3619,7 +2209,8 @@ ofp_to_string__(const struct ofp_header *oh,
                                     verbosity);
 
     case OFPTYPE_FLOW_MOD:
-        return ofp_print_flow_mod(string, oh, port_map, table_map, verbosity);
+        return ofputil_flow_mod_format(string, oh, port_map, table_map,
+                                       verbosity);
 
     case OFPTYPE_PORT_MOD:
         return ofp_print_port_mod(string, oh, port_map);
diff --git a/lib/ofp-switch.c b/lib/ofp-switch.c
index 3cd0fcae833f..1fbfabed5590 100644
--- a/lib/ofp-switch.c
+++ b/lib/ofp-switch.c
@@ -22,6 +22,7 @@
 #include "openvswitch/ofp-errors.h"
 #include "openvswitch/ofp-msgs.h"
 #include "openvswitch/ofp-port.h"
+#include "openvswitch/ofp-print.h"
 #include "util.h"
 
 /* ofputil_switch_features */
@@ -229,6 +230,53 @@ ofputil_put_switch_features_port(const struct ofputil_phy_port *pp,
     }
 }
 
+static const char *
+ofputil_capabilities_to_name(uint32_t bit)
+{
+    enum ofputil_capabilities capabilities = bit;
+
+    switch (capabilities) {
+    case OFPUTIL_C_FLOW_STATS:   return "FLOW_STATS";
+    case OFPUTIL_C_TABLE_STATS:  return "TABLE_STATS";
+    case OFPUTIL_C_PORT_STATS:   return "PORT_STATS";
+    case OFPUTIL_C_IP_REASM:     return "IP_REASM";
+    case OFPUTIL_C_QUEUE_STATS:  return "QUEUE_STATS";
+    case OFPUTIL_C_ARP_MATCH_IP: return "ARP_MATCH_IP";
+    case OFPUTIL_C_STP:          return "STP";
+    case OFPUTIL_C_GROUP_STATS:  return "GROUP_STATS";
+    case OFPUTIL_C_PORT_BLOCKED: return "PORT_BLOCKED";
+    case OFPUTIL_C_BUNDLES:      return "BUNDLES";
+    case OFPUTIL_C_FLOW_MONITORING: return "FLOW_MONITORING";
+    }
+
+    return NULL;
+}
+
+void
+ofputil_switch_features_format(struct ds *s,
+                               const struct ofputil_switch_features *features)
+{
+    ds_put_format(s, " dpid:%016"PRIx64"\n", features->datapath_id);
+
+    ds_put_format(s, "n_tables:%"PRIu8", n_buffers:%"PRIu32,
+                  features->n_tables, features->n_buffers);
+    if (features->auxiliary_id) {
+        ds_put_format(s, ", auxiliary_id:%"PRIu8, features->auxiliary_id);
+    }
+    ds_put_char(s, '\n');
+
+    ds_put_cstr(s, "capabilities: ");
+    ofp_print_bit_names(s, features->capabilities,
+                        ofputil_capabilities_to_name, ' ');
+    ds_put_char(s, '\n');
+
+    if (features->ofpacts) {
+        ds_put_cstr(s, "actions: ");
+        ofpact_bitmap_format(features->ofpacts, s);
+        ds_put_char(s, '\n');
+    }
+}
+
 const char *
 ofputil_frag_handling_to_string(enum ofputil_frag_handling frag)
 {
@@ -334,3 +382,17 @@ ofputil_encode_set_config(const struct ofputil_switch_config *config,
     struct ofpbuf *b = ofpraw_alloc(OFPRAW_OFPT_SET_CONFIG, version, 0);
     return ofputil_put_switch_config(config, b);
 }
+
+void
+ofputil_switch_config_format(struct ds *s,
+                             const struct ofputil_switch_config *config)
+{
+    ds_put_format(s, " frags=%s",
+                  ofputil_frag_handling_to_string(config->frag));
+
+    if (config->invalid_ttl_to_controller > 0) {
+        ds_put_format(s, " invalid_ttl_to_controller");
+    }
+
+    ds_put_format(s, " miss_send_len=%"PRIu16"\n", config->miss_send_len);
+}
diff --git a/lib/ofp-table.c b/lib/ofp-table.c
index 558e4bcd9127..7df7167deddb 100644
--- a/lib/ofp-table.c
+++ b/lib/ofp-table.c
@@ -22,6 +22,7 @@
 #include "openvswitch/json.h"
 #include "openvswitch/ofp-actions.h"
 #include "openvswitch/ofp-msgs.h"
+#include "openvswitch/ofp-print.h"
 #include "openvswitch/ofp-prop.h"
 #include "openvswitch/ofpbuf.h"
 #include "openvswitch/vlog.h"
@@ -40,6 +41,42 @@ static enum ofputil_table_vacancy ofputil_decode_table_vacancy(
 static enum ofputil_table_eviction ofputil_decode_table_eviction(
     ovs_be32 config, enum ofp_version);
 
+const char *
+ofputil_table_miss_to_string(enum ofputil_table_miss miss)
+{
+    switch (miss) {
+    case OFPUTIL_TABLE_MISS_DEFAULT: return "default";
+    case OFPUTIL_TABLE_MISS_CONTROLLER: return "controller";
+    case OFPUTIL_TABLE_MISS_CONTINUE: return "continue";
+    case OFPUTIL_TABLE_MISS_DROP: return "drop";
+    default: return "***error***";
+    }
+}
+
+const char *
+ofputil_table_eviction_to_string(enum ofputil_table_eviction eviction)
+{
+    switch (eviction) {
+    case OFPUTIL_TABLE_EVICTION_DEFAULT: return "default";
+    case OFPUTIL_TABLE_EVICTION_ON: return "on";
+    case OFPUTIL_TABLE_EVICTION_OFF: return "off";
+    default: return "***error***";
+    }
+
+}
+
+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***";
+    }
+
+}
+
 /* ofputil_table_map.  */
 
 void
@@ -708,6 +745,58 @@ ofputil_append_table_desc_reply(const struct ofputil_table_desc *td,
     ofpmp_postappend(replies, start_otd);
 }
 
+static const char *
+ofputil_eviction_flag_to_string(uint32_t bit)
+{
+    enum ofp14_table_mod_prop_eviction_flag eviction_flag = bit;
+
+    switch (eviction_flag) {
+    case OFPTMPEF14_OTHER:      return "OTHER";
+    case OFPTMPEF14_IMPORTANCE: return "IMPORTANCE";
+    case OFPTMPEF14_LIFETIME:   return "LIFETIME";
+    }
+
+    return NULL;
+}
+
+/* Appends to 'string' a description of the bitmap of OFPTMPEF14_* values in
+ * 'eviction_flags'. */
+static void
+ofputil_put_eviction_flags(struct ds *string, uint32_t eviction_flags)
+{
+    if (eviction_flags != UINT32_MAX) {
+        ofp_print_bit_names(string, eviction_flags,
+                            ofputil_eviction_flag_to_string, '|');
+    } else {
+        ds_put_cstr(string, "(default)");
+    }
+}
+
+void
+ofputil_table_desc_format(struct ds *s, const struct ofputil_table_desc *td,
+                          const struct ofputil_table_map *table_map)
+{
+    ds_put_format(s, "\n  table ");
+    ofputil_format_table(td->table_id, table_map, s);
+    ds_put_cstr(s, ":\n");
+    ds_put_format(s, "   eviction=%s eviction_flags=",
+                  ofputil_table_eviction_to_string(td->eviction));
+    ofputil_put_eviction_flags(s, td->eviction_flags);
+    ds_put_char(s, '\n');
+    ds_put_format(s, "   vacancy=%s",
+                  ofputil_table_vacancy_to_string(td->vacancy));
+    if (td->vacancy == OFPUTIL_TABLE_VACANCY_ON) {
+        ds_put_format(s, " vacancy_down=%"PRIu8"%%",
+                      td->table_vacancy.vacancy_down);
+        ds_put_format(s, " vacancy_up=%"PRIu8"%%",
+                      td->table_vacancy.vacancy_up);
+        ds_put_format(s, " vacancy=%"PRIu8"%%",
+                      td->table_vacancy.vacancy);
+    }
+    ds_put_char(s, '\n');
+}
+
+
 /* This function parses Vacancy property, and decodes the
  * ofp14_table_mod_prop_vacancy in ofputil_table_mod.
  * Returns OFPERR_OFPBPC_BAD_VALUE error code when vacancy_down is
@@ -982,13 +1071,47 @@ ofputil_encode_table_mod(const struct ofputil_table_mod *tm,
     return b;
 }
 
+void
+ofputil_table_mod_format(struct ds *s, const struct ofputil_table_mod *tm,
+                         const struct ofputil_table_map *table_map)
+{
+    if (tm->table_id == 0xff) {
+        ds_put_cstr(s, " table_id: ALL_TABLES");
+    } else {
+        ds_put_format(s, " table_id=");
+        ofputil_format_table(tm->table_id, table_map, s);
+    }
+
+    if (tm->miss != OFPUTIL_TABLE_MISS_DEFAULT) {
+        ds_put_format(s, ", flow_miss_config=%s",
+                      ofputil_table_miss_to_string(tm->miss));
+    }
+    if (tm->eviction != OFPUTIL_TABLE_EVICTION_DEFAULT) {
+        ds_put_format(s, ", eviction=%s",
+                      ofputil_table_eviction_to_string(tm->eviction));
+    }
+    if (tm->eviction_flags != UINT32_MAX) {
+        ds_put_cstr(s, "eviction_flags=");
+        ofputil_put_eviction_flags(s, tm->eviction_flags);
+    }
+    if (tm->vacancy != OFPUTIL_TABLE_VACANCY_DEFAULT) {
+        ds_put_format(s, ", vacancy=%s",
+                      ofputil_table_vacancy_to_string(tm->vacancy));
+        if (tm->vacancy == OFPUTIL_TABLE_VACANCY_ON) {
+            ds_put_format(s, " vacancy:%"PRIu8""
+                          ",%"PRIu8"", tm->table_vacancy.vacancy_down,
+                          tm->table_vacancy.vacancy_up);
+        }
+    }
+}
+
 /* Convert 'setting' (as described for the "mod-table" command
  * in ovs-ofctl man page) into 'tm->table_vacancy->vacancy_up' and
  * 'tm->table_vacancy->vacancy_down' threshold values.
  * For the two threshold values, value of vacancy_up is always greater
  * than value of vacancy_down.
  *
- * Returns NULL if successful, otherwise a malloc()'d string describing the
+ * Returns NULL if successful, otherwise a malloc()'d s describing the
  * error.  The caller is responsible for freeing the returned string. */
 static char * OVS_WARN_UNUSED_RESULT
 parse_ofp_table_vacancy(struct ofputil_table_mod *tm, const char *setting)
@@ -1107,6 +1230,272 @@ parse_ofp_table_mod(struct ofputil_table_mod *tm, const char *table_id,
 
     return NULL;
 }
+
+static void
+print_table_action_features(struct ds *s,
+                            const struct ofputil_table_action_features *taf)
+{
+    if (taf->ofpacts) {
+        ds_put_cstr(s, "        actions: ");
+        ofpact_bitmap_format(taf->ofpacts, s);
+        ds_put_char(s, '\n');
+    }
+
+    if (!bitmap_is_all_zeros(taf->set_fields.bm, MFF_N_IDS)) {
+        int i;
+
+        ds_put_cstr(s, "        supported on Set-Field:");
+        BITMAP_FOR_EACH_1 (i, MFF_N_IDS, taf->set_fields.bm) {
+            ds_put_format(s, " %s", mf_from_id(i)->name);
+        }
+        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->ofpacts == b->ofpacts
+            && bitmap_equal(a->set_fields.bm, b->set_fields.bm, MFF_N_IDS));
+}
+
+static bool
+table_action_features_empty(const struct ofputil_table_action_features *taf)
+{
+    return !taf->ofpacts && bitmap_is_all_zeros(taf->set_fields.bm, MFF_N_IDS);
+}
+
+static void
+print_table_instruction_features(
+    struct ds *s,
+    const struct ofputil_table_instruction_features *tif,
+    const struct ofputil_table_instruction_features *prev_tif)
+{
+    int start, end;
+
+    if (!bitmap_is_all_zeros(tif->next, 255)) {
+        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');
+    }
+
+    if (tif->instructions) {
+        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)) {
+                    const char *name = ovs_instruction_name_from_type(i);
+                    if (name) {
+                        ds_put_cstr(s, name);
+                    } else {
+                        ds_put_format(s, "%d", i);
+                    }
+                    ds_put_char(s, ',');
+                }
+            }
+            ds_chomp(s, ',');
+            ds_put_char(s, '\n');
+        }
+    }
+
+    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");
+        print_table_action_features(s, &tif->apply);
+    } else if (tif->write.ofpacts
+               || !bitmap_is_all_zeros(tif->write.set_fields.bm, MFF_N_IDS)) {
+        ds_put_cstr(s, "      Write-Actions and Apply-Actions features:\n");
+        print_table_action_features(s, &tif->write);
+    }
+}
+
+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 bool
+table_instruction_features_empty(
+    const struct ofputil_table_instruction_features *tif)
+{
+    return (bitmap_is_all_zeros(tif->next, 255)
+            && !tif->instructions
+            && table_action_features_empty(&tif->write)
+            && table_action_features_empty(&tif->apply));
+}
+
+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
+ofputil_table_features_format(
+    struct ds *s,
+    const struct ofputil_table_features *features,
+    const struct ofputil_table_features *prev_features,
+    const struct ofputil_table_stats *stats,
+    const struct ofputil_table_stats *prev_stats,
+    const struct ofputil_table_map *table_map)
+{
+    int i;
+
+    ds_put_format(s, "  table ");
+    ofputil_format_table(features->table_id, table_map, s);
+    if (features->name[0]) {
+        ds_put_format(s, " (\"%s\")", features->name);
+    }
+    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 (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));
+    }
+
+    if (features->miss_config != OFPUTIL_TABLE_MISS_DEFAULT) {
+        ds_put_format(s, "    config=%s\n",
+                      ofputil_table_miss_to_string(features->miss_config));
+    }
+
+    if (features->supports_eviction >= 0) {
+        ds_put_format(s, "    eviction: %ssupported\n",
+                      features->supports_eviction ? "" : "not ");
+
+    }
+    if (features->supports_vacancy_events >= 0) {
+        ds_put_format(s, "    vacancy events: %ssupported\n",
+                      features->supports_vacancy_events ? "" : "not ");
+
+    }
+
+    if (features->max_entries) {
+        ds_put_format(s, "    max_entries=%"PRIu32"\n", features->max_entries);
+    }
+
+    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, prev_nonmiss);
+        ds_put_cstr(s, "    instructions (table miss):\n");
+        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, prev_nonmiss);
+    }
+
+    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"));
+            }
+        }
+    }
+}
 
 /* Table stats. */
 
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index c78c856eaf81..ebbee077c81e 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -173,6 +173,23 @@ ofputil_encode_hello(uint32_t allowed_versions)
 
     return msg;
 }
+
+void
+ofputil_hello_format(struct ds *string, const struct ofp_header *oh)
+{
+    uint32_t allowed_versions;
+    bool ok;
+
+    ok = ofputil_decode_hello(oh, &allowed_versions);
+
+    ds_put_cstr(string, "\n version bitmap: ");
+    ofputil_format_version_bitmap(string, allowed_versions);
+
+    if (!ok) {
+        ds_put_cstr(string, "\n unknown data in hello:\n");
+        ds_put_hex_dump(string, oh, ntohs(oh->length), 0, true);
+    }
+}
 
 /* Creates and returns an OFPT_ECHO_REQUEST message with an empty payload. */
 struct ofpbuf *
diff --git a/ovn/utilities/ovn-sbctl.c b/ovn/utilities/ovn-sbctl.c
index 5764ee336837..0bef3adf68b3 100644
--- a/ovn/utilities/ovn-sbctl.c
+++ b/ovn/utilities/ovn-sbctl.c
@@ -797,7 +797,7 @@ sbctl_dump_openflow(struct vconn *vconn, const struct uuid *uuid, bool stats)
 
             ds_clear(&s);
             if (stats) {
-                ofp_print_flow_stats(&s, fs, NULL, NULL, true);
+                ofputil_flow_stats_format(&s, fs, NULL, NULL, true);
             } else {
                 ds_put_format(&s, "%stable=%s%"PRIu8" ",
                               colors.special, colors.end, fs->table_id);
diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c
index db39c49618be..593108ae47f4 100644
--- a/ovn/utilities/ovn-trace.c
+++ b/ovn/utilities/ovn-trace.c
@@ -1937,7 +1937,7 @@ trace_openflow(const struct ovntrace_flow *f, struct ovs_list *super)
         struct ds s = DS_EMPTY_INITIALIZER;
         for (size_t i = 0; i < n_fses; i++) {
             ds_clear(&s);
-            ofp_print_flow_stats(&s, &fses[i], NULL, NULL, true);
+            ofputil_flow_stats_format(&s, &fses[i], NULL, NULL, true);
             ovntrace_node_append(super, OVNTRACE_NODE_ACTION,
                                  "%s", ds_cstr(&s));
         }
diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
index 8b3817c03f90..a3d3efe5f59e 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -922,9 +922,9 @@ ofctl_dump_table_features(struct ovs_cmdl_context *ctx)
                     }
 
                     struct ds s = DS_EMPTY_INITIALIZER;
-                    ofp_print_table_features(&s, &tf, n ? &prev : NULL,
-                                             NULL, NULL,
-                                             tables_to_show(ctx->argv[1]));
+                    ofputil_table_features_format(
+                        &s, &tf, n ? &prev : NULL, NULL, NULL,
+                        tables_to_show(ctx->argv[1]));
                     puts(ds_cstr(&s));
                     ds_destroy(&s);
 
@@ -1567,8 +1567,10 @@ ofctl_dump_flows(struct ovs_cmdl_context *ctx)
         struct ds s = DS_EMPTY_INITIALIZER;
         for (size_t i = 0; i < n_fses; i++) {
             ds_clear(&s);
-            ofp_print_flow_stats(&s, &fses[i], ports_to_show(ctx->argv[1]),
-                                 tables_to_show(ctx->argv[1]), show_stats);
+            ofputil_flow_stats_format(&s, &fses[i],
+                                      ports_to_show(ctx->argv[1]),
+                                      tables_to_show(ctx->argv[1]),
+                                      show_stats);
             printf(" %s\n", ds_cstr(&s));
         }
         ds_destroy(&s);
