diff --git a/doc/guides/prog_guide/rte_flow.rst b/doc/guides/prog_guide/rte_flow.rst
index 565a809..5026730 100644
--- a/doc/guides/prog_guide/rte_flow.rst
+++ b/doc/guides/prog_guide/rte_flow.rst
@@ -1697,6 +1697,25 @@ This function initializes ``error`` (if non-NULL) with the provided
 parameters and sets ``rte_errno`` to ``code``. A negative error ``code`` is
 then returned.
 
+Object conversion
+~~~~~~~~~~~~~~~~~
+
+.. code-block:: c
+
+   int
+   rte_flow_conv(enum rte_flow_conv_op op,
+                 void *dst,
+                 size_t size,
+                 const void *src,
+                 struct rte_flow_error *error);
+
+Convert ``src`` to ``dst`` according to operation ``op``. Possible
+operations include:
+
+- Attributes, pattern item or action duplication.
+- Duplication of an entire pattern or list of actions.
+- Duplication of a complete flow rule description.
+
 Caveats
 -------
 
diff --git a/drivers/net/failsafe/failsafe_ether.c b/drivers/net/failsafe/failsafe_ether.c
index 0c0748f..80f391a 100644
--- a/drivers/net/failsafe/failsafe_ether.c
+++ b/drivers/net/failsafe/failsafe_ether.c
@@ -257,9 +257,9 @@ fs_eth_dev_conf_apply(struct rte_eth_dev *dev,
 			DEBUG("Creating flow #%" PRIu32, i++);
 			flow->flows[SUB_ID(sdev)] =
 				rte_flow_create(PORT_ID(sdev),
-						&flow->fd->attr,
-						flow->fd->items,
-						flow->fd->actions,
+						flow->fd.attr,
+						flow->fd.pattern,
+						flow->fd.actions,
 						&ferror);
 			ret = rte_errno;
 			if (ret)
diff --git a/drivers/net/failsafe/failsafe_flow.c b/drivers/net/failsafe/failsafe_flow.c
index 153ceee..a568a8b 100644
--- a/drivers/net/failsafe/failsafe_flow.c
+++ b/drivers/net/failsafe/failsafe_flow.c
@@ -31,8 +31,11 @@
  *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include <stddef.h>
+#include <string.h>
 #include <sys/queue.h>
 
+#include <rte_errno.h>
 #include <rte_malloc.h>
 #include <rte_tailq.h>
 #include <rte_flow.h>
@@ -46,19 +49,31 @@ fs_flow_allocate(const struct rte_flow_attr *attr,
 		 const struct rte_flow_action *actions)
 {
 	struct rte_flow *flow;
-	size_t fdsz;
+	const struct rte_flow_conv_rule rule = {
+		{ attr }, { items }, { actions },
+	};
+	struct rte_flow_error error;
+	int ret;
 
-	fdsz = rte_flow_copy(NULL, 0, attr, items, actions);
-	flow = rte_zmalloc(NULL,
-			   sizeof(struct rte_flow) + fdsz,
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_RULE, NULL, 0, &rule, &error);
+	if (ret < 0) {
+		ERROR("Unable to compute flow description size (%s): %s",
+		      error.message ? error.message : "unspecified",
+		      strerror(rte_errno));
+		return NULL;
+	}
+	flow = rte_zmalloc(NULL, offsetof(struct rte_flow, fd) + ret,
 			   RTE_CACHE_LINE_SIZE);
 	if (flow == NULL) {
 		ERROR("Could not allocate new flow");
 		return NULL;
 	}
-	flow->fd = (void *)((uintptr_t)flow + sizeof(*flow));
-	if (rte_flow_copy(flow->fd, fdsz, attr, items, actions) != fdsz) {
-		ERROR("Failed to copy flow description");
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_RULE, &flow->fd, ret, &rule,
+			    &error);
+	if (ret < 0) {
+		ERROR("Failed to copy flow description (%s): %s",
+		      error.message ? error.message : "unspecified",
+		      strerror(rte_errno));
 		rte_free(flow);
 		return NULL;
 	}
diff --git a/drivers/net/failsafe/failsafe_private.h b/drivers/net/failsafe/failsafe_private.h
index d2d92af..f40d649 100644
--- a/drivers/net/failsafe/failsafe_private.h
+++ b/drivers/net/failsafe/failsafe_private.h
@@ -40,6 +40,7 @@
 #include <rte_dev.h>
 #include <rte_ethdev.h>
 #include <rte_devargs.h>
+#include <rte_flow.h>
 
 #define FAILSAFE_DRIVER_NAME "Fail-safe PMD"
 
@@ -82,7 +83,8 @@ struct rte_flow {
 	/* sub_flows */
 	struct rte_flow *flows[FAILSAFE_MAX_ETHPORTS];
 	/* flow description for synchronization */
-	struct rte_flow_desc *fd;
+	struct rte_flow_conv_rule fd;
+	uint8_t fd_data[];
 };
 
 enum dev_state {
diff --git a/lib/librte_ether/rte_ethdev_version.map b/lib/librte_ether/rte_ethdev_version.map
index 07f9e17..673b570 100644
--- a/lib/librte_ether/rte_ethdev_version.map
+++ b/lib/librte_ether/rte_ethdev_version.map
@@ -192,5 +192,6 @@ DPDK_17.11 {
 	global:
 
 	rte_eth_dev_reset;
+	rte_flow_conv;
 
 } DPDK_17.08;
diff --git a/lib/librte_ether/rte_flow.c b/lib/librte_ether/rte_flow.c
index 34ce516..03a4d35 100644
--- a/lib/librte_ether/rte_flow.c
+++ b/lib/librte_ether/rte_flow.c
@@ -299,110 +299,206 @@ flow_action_conf_size(const struct rte_flow_action *action,
 	*pad = RTE_ALIGN_CEIL(*size, sizeof(double)) - *size;
 }
 
-/** Store a full rte_flow description. */
-size_t
-rte_flow_copy(struct rte_flow_desc *desc, size_t len,
-	      const struct rte_flow_attr *attr,
-	      const struct rte_flow_item *items,
-	      const struct rte_flow_action *actions)
+/** Internal helper to convert a pattern. */
+static int
+rte_flow_conv_pattern(struct rte_flow_item *dst,
+		      size_t size,
+		      const struct rte_flow_item *src,
+		      unsigned int num,
+		      struct rte_flow_error *error)
 {
-	struct rte_flow_desc *fd = NULL;
-	size_t tmp;
-	size_t pad;
-	size_t off1 = 0;
-	size_t off2 = 0;
-	size_t size = 0;
+	uint8_t *data = NULL;
+	int store = 0;
+	size_t min;
+	size_t off;
+	unsigned int i;
 
 store:
-	if (items) {
-		const struct rte_flow_item *item;
-
-		item = items;
-		if (fd)
-			fd->items = (void *)&fd->data[off1];
-		do {
-			struct rte_flow_item *dst = NULL;
-
-			if ((size_t)item->type >=
-				RTE_DIM(rte_flow_desc_item) ||
-			    !rte_flow_desc_item[item->type].name) {
-				rte_errno = ENOTSUP;
-				return 0;
-			}
-			if (fd)
-				dst = memcpy(fd->data + off1, item,
-					     sizeof(*item));
-			off1 += sizeof(*item);
-			flow_item_spec_size(item, &tmp, &pad);
-			if (item->spec) {
-				if (fd)
-					dst->spec = memcpy(fd->data + off2,
-							   item->spec, tmp);
-				off2 += tmp + pad;
-			}
-			if (item->last) {
-				if (fd)
-					dst->last = memcpy(fd->data + off2,
-							   item->last, tmp);
-				off2 += tmp + pad;
-			}
-			if (item->mask) {
-				if (fd)
-					dst->mask = memcpy(fd->data + off2,
-							   item->mask, tmp);
-				off2 += tmp + pad;
-			}
-			off2 = RTE_ALIGN_CEIL(off2, sizeof(double));
-		} while ((item++)->type != RTE_FLOW_ITEM_TYPE_END);
-		off1 = RTE_ALIGN_CEIL(off1, sizeof(double));
+	for (off = 0, i = 0; !num || i != num; ++i, ++src, ++dst) {
+		size_t spec;
+		size_t pad;
+
+		if ((size_t)src->type >= RTE_DIM(rte_flow_desc_item) ||
+		    !rte_flow_desc_item[src->type].name)
+			goto notsup;
+		if (store)
+			*dst = (struct rte_flow_item){ .type = src->type, };
+		flow_item_spec_size(src, &spec, &pad);
+		if (spec)
+			off = RTE_ALIGN_CEIL(off, sizeof(double));
+		if (src->spec) {
+			if (store)
+				dst->spec = memcpy(data + off, src->spec, spec);
+			off += spec;
+		}
+		if (src->last) {
+			off += pad;
+			if (store)
+				dst->last = memcpy(data + off, src->last, spec);
+			off += spec;
+		}
+		if (src->mask) {
+			off += pad;
+			if (store)
+				dst->mask = memcpy(data + off, src->mask, spec);
+			off += spec;
+		}
+		if (src->type == RTE_FLOW_ITEM_TYPE_END)
+			num = i + 1;
 	}
-	if (actions) {
-		const struct rte_flow_action *action;
-
-		action = actions;
-		if (fd)
-			fd->actions = (void *)&fd->data[off1];
-		do {
-			struct rte_flow_action *dst = NULL;
-
-			if ((size_t)action->type >=
-				RTE_DIM(rte_flow_desc_action) ||
-			    !rte_flow_desc_action[action->type].name) {
-				rte_errno = ENOTSUP;
-				return 0;
-			}
-			if (fd)
-				dst = memcpy(fd->data + off1, action,
-					     sizeof(*action));
-			off1 += sizeof(*action);
-			flow_action_conf_size(action, &tmp, &pad);
-			if (action->conf) {
-				if (fd)
-					dst->conf = memcpy(fd->data + off2,
-							   action->conf, tmp);
-				off2 += tmp + pad;
-			}
-			off2 = RTE_ALIGN_CEIL(off2, sizeof(double));
-		} while ((action++)->type != RTE_FLOW_ACTION_TYPE_END);
+	min = RTE_ALIGN_CEIL(sizeof(*src) * num, sizeof(double)) + off;
+	if (store || !size)
+		return min;
+	if (min > size)
+		goto nomem;
+	src -= i;
+	dst -= i;
+	data = (void *)((uintptr_t)dst + min - off);
+	store = 1;
+	goto store;
+notsup:
+	return rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ITEM, src,
+				  "cannot convert unknown item type");
+nomem:
+	return rte_flow_error_set(error, ENOMEM, RTE_FLOW_ERROR_TYPE_ITEM, src,
+				  "not enough room to store pattern item");
+}
+
+/** Internal helper to convert a list of actions. */
+static int
+rte_flow_conv_actions(struct rte_flow_action *dst,
+		      size_t size,
+		      const struct rte_flow_action *src,
+		      unsigned int num,
+		      struct rte_flow_error *error)
+{
+	uint8_t *data = NULL;
+	int store = 0;
+	size_t min;
+	size_t off;
+	unsigned int i;
+
+store:
+	for (off = 0, i = 0; !num || i != num; ++i, ++src, ++dst) {
+		size_t conf;
+		size_t pad;
+
+		if ((size_t)src->type >= RTE_DIM(rte_flow_desc_action) ||
+		    !rte_flow_desc_action[src->type].name)
+			goto notsup;
+		if (store)
+			*dst = (struct rte_flow_action){ .type = src->type, };
+		flow_action_conf_size(src, &conf, &pad);
+		if (conf)
+			off = RTE_ALIGN_CEIL(off, sizeof(double));
+		if (store && conf)
+			dst->conf = memcpy(data + off, src->conf, conf);
+		off += conf;
+		if (src->type == RTE_FLOW_ACTION_TYPE_END)
+			num = i + 1;
 	}
-	if (fd != NULL)
-		return size;
-	off1 = RTE_ALIGN_CEIL(off1, sizeof(double));
-	tmp = RTE_ALIGN_CEIL(offsetof(struct rte_flow_desc, data),
-			     sizeof(double));
-	size = tmp + off1 + off2;
-	if (size > len)
-		return size;
-	fd = desc;
-	if (fd != NULL) {
-		*fd = (const struct rte_flow_desc) {
-			.size = size,
-			.attr = *attr,
-		};
-		tmp -= offsetof(struct rte_flow_desc, data);
-		off2 = tmp + off1;
-		off1 = tmp;
-		goto store;
+	min = RTE_ALIGN_CEIL(sizeof(*src) * num, sizeof(double)) + off;
+	if (store || !size)
+		return min;
+	if (min > size)
+		goto nomem;
+	src -= i;
+	dst -= i;
+	data = (void *)((uintptr_t)dst + min - off);
+	store = 1;
+	goto store;
+notsup:
+	return rte_flow_error_set(error, ENOTSUP, RTE_FLOW_ERROR_TYPE_ACTION,
+				  src, "cannot convert unknown action type");
+nomem:
+	return rte_flow_error_set(error, ENOMEM, RTE_FLOW_ERROR_TYPE_ACTION,
+				  src, "not enough room to store action");
+}
+
+/** Internal helper to convert a flow rule description. */
+static int
+rte_flow_conv_rule(struct rte_flow_conv_rule *dst,
+		   size_t size,
+		   const struct rte_flow_conv_rule *src,
+		   struct rte_flow_error *error)
+{
+	size_t min;
+	int ret;
+
+	/* Attributes. */
+	min = RTE_ALIGN_CEIL((uintptr_t)dst + sizeof(*dst),
+			     sizeof(double)) - (uintptr_t)dst;
+	if (size && size < min)
+		goto nomem;
+	if (size) {
+		dst->attr = (void *)((uintptr_t)dst + min);
+		*dst->attr = *src->attr_ro;
+	}
+	/* Pattern. */
+	min = RTE_ALIGN_CEIL((uintptr_t)dst + min + sizeof(*dst->attr),
+			     sizeof(double)) - (uintptr_t)dst;
+	if (size && size < min)
+		goto nomem;
+	ret = rte_flow_conv_pattern((void *)((uintptr_t)dst + min),
+				    size ? size - min : 0,
+				    src->pattern_ro, 0, error);
+	if (ret < 0)
+		return ret;
+	if (size)
+		dst->pattern = (void *)((uintptr_t)dst + min);
+	/* Actions. */
+	min = RTE_ALIGN_CEIL((uintptr_t)dst + min + ret,
+			     sizeof(double)) - (uintptr_t)dst;
+	if (size && size < min)
+		goto nomem;
+	ret = rte_flow_conv_actions((void *)((uintptr_t)dst + min),
+				    size ? size - min : 0,
+				    src->actions_ro, 0, error);
+	if (ret < 0)
+		return ret;
+	if (size)
+		dst->actions = (void *)((uintptr_t)dst + min);
+	return min + ret;
+nomem:
+	return rte_flow_error_set
+		(error, ENOMEM, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+		 "not enough room for alignment padding");
+}
+
+/** Helper function to convert flow API objects. */
+int
+rte_flow_conv(enum rte_flow_conv_op op,
+	      void *dst,
+	      size_t size,
+	      const void *src,
+	      struct rte_flow_error *error)
+{
+	switch (op) {
+		const struct rte_flow_attr *attr;
+
+	case RTE_FLOW_CONV_OP_NONE:
+		return 0;
+	case RTE_FLOW_CONV_OP_ATTR:
+		attr = src;
+		if (size && size < sizeof(*attr))
+			return rte_flow_error_set
+				(error, ENOMEM, RTE_FLOW_ERROR_TYPE_ATTR, src,
+				 "not enough room to store attributes");
+		if (size)
+			memcpy(dst, attr, sizeof(*attr));
+		return sizeof(*attr);
+	case RTE_FLOW_CONV_OP_ITEM:
+		return rte_flow_conv_pattern(dst, size, src, 1, error);
+	case RTE_FLOW_CONV_OP_ACTION:
+		return rte_flow_conv_actions(dst, size, src, 1, error);
+	case RTE_FLOW_CONV_OP_PATTERN:
+		return rte_flow_conv_pattern(dst, size, src, 0, error);
+	case RTE_FLOW_CONV_OP_ACTIONS:
+		return rte_flow_conv_actions(dst, size, src, 0, error);
+	case RTE_FLOW_CONV_OP_RULE:
+		return rte_flow_conv_rule(dst, size, src, error);
 	}
-	return 0;
+	return rte_flow_error_set
+		(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+		 "unknown object conversion operation");
 }
diff --git a/lib/librte_ether/rte_flow.h b/lib/librte_ether/rte_flow.h
index ec42f02..3d06bdc 100644
--- a/lib/librte_ether/rte_flow.h
+++ b/lib/librte_ether/rte_flow.h
@@ -42,7 +42,10 @@
  * associated actions in hardware through flow rules.
  */
 
+#include <stddef.h>
+
 #include <rte_arp.h>
+#include <rte_common.h>
 #include <rte_errno.h>
 #include <rte_ether.h>
 #include <rte_icmp.h>
@@ -1067,6 +1070,119 @@ struct rte_flow_error {
 };
 
 /**
+ * Complete flow rule description.
+ *
+ * This object type is used when converting a flow rule description.
+ *
+ * @see RTE_FLOW_CONV_OP_RULE
+ * @see rte_flow_conv()
+ */
+RTE_STD_C11
+struct rte_flow_conv_rule {
+	union {
+		const struct rte_flow_attr *attr_ro; /**< RO attributes. */
+		struct rte_flow_attr *attr; /**< Attributes. */
+	};
+	union {
+		const struct rte_flow_item *pattern_ro; /**< RO pattern. */
+		struct rte_flow_item *pattern; /**< Pattern items. */
+	};
+	union {
+		const struct rte_flow_action *actions_ro; /**< RO actions. */
+		struct rte_flow_action *actions; /**< List of actions. */
+	};
+};
+
+/**
+ * Conversion operations for flow API objects.
+ *
+ * @see rte_flow_conv()
+ */
+enum rte_flow_conv_op {
+	/**
+	 * No operation to perform.
+	 *
+	 * rte_flow_conv() simply returns 0.
+	 */
+	RTE_FLOW_CONV_OP_NONE,
+
+	/**
+	 * Convert attributes structure.
+	 *
+	 * This is a basic copy of an attributes structure.
+	 *
+	 * - @p src type:
+	 *   @code const struct rte_flow_attr * @endcode
+	 * - @p dst type:
+	 *   @code struct rte_flow_attr * @endcode
+	 */
+	RTE_FLOW_CONV_OP_ATTR,
+
+	/**
+	 * Convert a single item.
+	 *
+	 * Duplicates @p spec, @p last and @p mask but not outside objects.
+	 *
+	 * - @p src type:
+	 *   @code const struct rte_flow_item * @endcode
+	 * - @p dst type:
+	 *   @code struct rte_flow_item * @endcode
+	 */
+	RTE_FLOW_CONV_OP_ITEM,
+
+	/**
+	 * Convert a single action.
+	 *
+	 * Duplicates @p conf but not outside objects.
+	 *
+	 * - @p src type:
+	 *   @code const struct rte_flow_action * @endcode
+	 * - @p dst type:
+	 *   @code struct rte_flow_action * @endcode
+	 */
+	RTE_FLOW_CONV_OP_ACTION,
+
+	/**
+	 * Convert an entire pattern.
+	 *
+	 * Duplicates all pattern items at once with the same constraints as
+	 * RTE_FLOW_CONV_OP_ITEM.
+	 *
+	 * - @p src type:
+	 *   @code const struct rte_flow_item * @endcode
+	 * - @p dst type:
+	 *   @code struct rte_flow_item * @endcode
+	 */
+	RTE_FLOW_CONV_OP_PATTERN,
+
+	/**
+	 * Convert a list of actions.
+	 *
+	 * Duplicates the entire list of actions at once with the same
+	 * constraints as RTE_FLOW_CONV_OP_ACTION.
+	 *
+	 * - @p src type:
+	 *   @code const struct rte_flow_action * @endcode
+	 * - @p dst type:
+	 *   @code struct rte_flow_action * @endcode
+	 */
+	RTE_FLOW_CONV_OP_ACTIONS,
+
+	/**
+	 * Convert a complete flow rule description.
+	 *
+	 * Comprises attributes, pattern and actions together at once with
+	 * the usual constraints.
+	 *
+	 * - @p src type:
+	 *   @code const struct rte_flow_conv_rule * @endcode
+	 * - @p dst type:
+	 *   @code struct rte_flow_conv_rule * @endcode
+	 */
+	RTE_FLOW_CONV_OP_RULE,
+};
+
+/**
  * Check whether a flow rule can be created on a given port.
  *
  * The flow rule is validated for correctness and whether it could be accepted
@@ -1306,44 +1422,55 @@ rte_flow_error_set(struct rte_flow_error *error,
 }
 
 /**
- * Generic flow representation.
+ * Flow object conversion helper.
  *
- * This form is sufficient to describe an rte_flow independently from any
- * PMD implementation and allows for replayability and identification.
- */
-struct rte_flow_desc {
-	size_t size; /**< Allocated space including data[]. */
-	struct rte_flow_attr attr; /**< Attributes. */
-	struct rte_flow_item *items; /**< Items. */
-	struct rte_flow_action *actions; /**< Actions. */
-	uint8_t data[]; /**< Storage for items/actions. */
-};
-
-/**
- * Copy an rte_flow rule description.
+ * This function performs conversion of various flow API objects to a
+ * pre-allocated destination buffer. See enum rte_flow_conv_op for possible
+ * operations and details about each of them.
  *
- * @param[in] fd
- *   Flow rule description.
- * @param[in] len
- *   Total size of allocated data for the flow description.
- * @param[in] attr
- *   Flow rule attributes.
- * @param[in] items
- *   Pattern specification (list terminated by the END pattern item).
- * @param[in] actions
- *   Associated actions (list terminated by the END action).
+ * Note that in most cases, "deep" copies stop at the boundary of the flow
+ * API. Outside objects are not converted and their pointers are copied
+ * unchanged (e.g. the rss_conf field in struct rte_flow_action_rss).
+ *
+ * Since the destination buffer must be large enough, this function works in
+ * a manner reminiscent of snprintf() as described below:
+ *
+ * - If @p size is 0, nothing is converted and @p dst may be a NULL pointer.
+ *
+ * - If @p size is large enough, conversion occurs and @p dst must be
+ *   non-NULL.
+ *
+ * - The returned value, if positive, is the number of bytes needed to store
+ *   the conversion of @p src in @p dst according to @p op.
+ *
+ * - Otherwise in case of error (e.g. @p size nonzero but not large enough),
+ *   a negative error code is returned.
+ *
+ * @param op
+ *   Operation to perform, related to the object type of @p dst.
+ * @param[out] dst
+ *   Destination buffer address. Must be suitably aligned by the caller.
+ * @param size
+ *   Destination buffer size in bytes.
+ * @param[in] src
+ *   Source object to copy. Its type may differ from that of @p dst.
+ * @param[out] error
+ *   Perform verbose error reporting if not NULL. It is initialized in case
+ *   of error only.
  *
  * @return
- *   If len is greater or equal to the size of the flow, the total size of the
- *   flow description and its data.
- *   If len is lower than the size of the flow, the number of bytes that would
- *   have been written to desc had it been sufficient. Nothing is written.
+ *   The number of bytes required to convert @p src on success, a negative
+ *   errno value otherwise and rte_errno is set.
+ *
+ * @see
+ *   rte_flow_conv_op
  */
-size_t
-rte_flow_copy(struct rte_flow_desc *fd, size_t len,
-	      const struct rte_flow_attr *attr,
-	      const struct rte_flow_item *items,
-	      const struct rte_flow_action *actions);
+int
+rte_flow_conv(enum rte_flow_conv_op op,
+	      void *dst,
+	      size_t size,
+	      const void *src,
+	      struct rte_flow_error *error);
 
 #ifdef __cplusplus
 }
