[ovs-dev,6/6] ofp-print: Move much of the printing code into message-specific files.

Message ID 20180216225445.28688-6-blp@ovn.org
State Accepted
Headers show
Series
  • [ovs-dev,1/6] ofp-util: Use consistent naming convention.
Related show

Commit Message

Ben Pfaff Feb. 16, 2018, 10:54 p.m.
Until now, the ofp-print code has had a lot of logic specific to
individual messages.  This code is better put with the other code specific
to those messages, so this commit starts to migrate it.

There is more work of a similar type to do, but this is a reasonable start.

Signed-off-by: Ben Pfaff <blp@ovn.org>
---
 include/openvswitch/netdev.h      |    2 +
 include/openvswitch/ofp-errors.h  |    6 +
 include/openvswitch/ofp-flow.h    |   17 +
 include/openvswitch/ofp-match.h   |    5 +
 include/openvswitch/ofp-monitor.h |   17 +-
 include/openvswitch/ofp-packet.h  |   11 +
 include/openvswitch/ofp-port.h    |   13 +-
 include/openvswitch/ofp-print.h   |   22 +-
 include/openvswitch/ofp-switch.h  |    5 +
 include/openvswitch/ofp-table.h   |   51 +-
 include/openvswitch/ofp-util.h    |    1 +
 lib/netdev.c                      |   35 +
 lib/ofp-errors.c                  |   19 +
 lib/ofp-flow.c                    |  257 ++++++
 lib/ofp-match.c                   |  154 ++++
 lib/ofp-monitor.c                 |  162 ++++
 lib/ofp-packet.c                  |  161 ++++
 lib/ofp-port.c                    |  218 +++++
 lib/ofp-print.c                   | 1609 +++----------------------------------
 lib/ofp-switch.c                  |   62 ++
 lib/ofp-table.c                   |  391 ++++++++-
 lib/ofp-util.c                    |   17 +
 ovn/utilities/ovn-sbctl.c         |    2 +-
 ovn/utilities/ovn-trace.c         |    2 +-
 utilities/ovs-ofctl.c             |   12 +-
 25 files changed, 1698 insertions(+), 1553 deletions(-)

Comments

Justin Pettit March 13, 2018, 11:04 p.m. | #1
> On Feb 16, 2018, at 2:54 PM, Ben Pfaff <blp@ovn.org> wrote:

It looks like this was mostly moving code around, so I didn't pore over the review, but let me know if you want me to take a closer look.  I did notice a few smaller things:

> 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
> ...
> +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***";
> +    }
> +
> +}

This seems to have an unnecessary blank line.

> +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***";
> +    }
> +
> +}

Same here.

> +void
> +ofputil_table_desc_format(struct ds *s, const struct ofputil_table_desc *td,
> +                          const struct ofputil_table_map *table_map)
> +{
> ...
> +}
> +
> +
> /* This function parses Vacancy property, and decodes the

And here.

> /* 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

I assume truncating "string" to "s" was unintentional.

Thanks for tackling "ofp-print.c".

--Justin
Justin Pettit March 13, 2018, 11:15 p.m. | #2
> On Mar 13, 2018, at 4:04 PM, Justin Pettit <jpettit@ovn.org> wrote:
> 
> 
>> On Feb 16, 2018, at 2:54 PM, Ben Pfaff <blp@ovn.org> wrote:
> 
> It looks like this was mostly moving code around, so I didn't pore over the review, but let me know if you want me to take a closer look.  I did notice a few smaller things:
> 
>> 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
>> ...
>> +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***";
>> +    }
>> +
>> +}
> 
> This seems to have an unnecessary blank line.
> 
>> +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***";
>> +    }
>> +
>> +}
> 
> Same here.
> 
>> +void
>> +ofputil_table_desc_format(struct ds *s, const struct ofputil_table_desc *td,
>> +                          const struct ofputil_table_map *table_map)
>> +{
>> ...
>> +}
>> +
>> +
>> /* This function parses Vacancy property, and decodes the
> 
> And here.
> 
>> /* 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
> 
> I assume truncating "string" to "s" was unintentional.
> 
> Thanks for tackling "ofp-print.c".
> 
> --Justin

Oh, and:

Acked-by: Justin Pettit <jpettit@ovn.org>

--Justin
Ben Pfaff March 14, 2018, 6:41 p.m. | #3
On Tue, Mar 13, 2018 at 04:15:08PM -0700, Justin Pettit wrote:
> 
> 
> > On Mar 13, 2018, at 4:04 PM, Justin Pettit <jpettit@ovn.org> wrote:
> > 
> > 
> >> On Feb 16, 2018, at 2:54 PM, Ben Pfaff <blp@ovn.org> wrote:
> > 
> > It looks like this was mostly moving code around, so I didn't pore over the review, but let me know if you want me to take a closer look.  I did notice a few smaller things:
> > 
> >> 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
> >> ...
> >> +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***";
> >> +    }
> >> +
> >> +}
> > 
> > This seems to have an unnecessary blank line.
> > 
> >> +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***";
> >> +    }
> >> +
> >> +}
> > 
> > Same here.
> > 
> >> +void
> >> +ofputil_table_desc_format(struct ds *s, const struct ofputil_table_desc *td,
> >> +                          const struct ofputil_table_map *table_map)
> >> +{
> >> ...
> >> +}
> >> +
> >> +
> >> /* This function parses Vacancy property, and decodes the
> > 
> > And here.
> > 
> >> /* 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
> > 
> > I assume truncating "string" to "s" was unintentional.
> > 
> > Thanks for tackling "ofp-print.c".
> > 
> > --Justin
> 
> Oh, and:
> 
> Acked-by: Justin Pettit <jpettit@ovn.org>
> 
> --Justin
> 
> 

Thanks.  I fixed up all of that and I'll apply this series to master in
a few minutes.

Patch

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);