+ usage();
+}
+\f
+/* replace-flows and diff-flows commands. */
+
+/* A flow table entry, possibly with two different versions. */
+struct fte {
+ struct cls_rule rule; /* Within a "struct classifier". */
+ struct fte_version *versions[2];
+};
+
+/* One version of a Flow Table Entry. */
+struct fte_version {
+ ovs_be64 cookie;
+ uint16_t idle_timeout;
+ uint16_t hard_timeout;
+ uint16_t flags;
+ union ofp_action *actions;
+ size_t n_actions;
+};
+
+/* Frees 'version' and the data that it owns. */
+static void
+fte_version_free(struct fte_version *version)
+{
+ if (version) {
+ free(version->actions);
+ free(version);
+ }
+}
+
+/* Returns true if 'a' and 'b' are the same, false if they differ.
+ *
+ * Ignores differences in 'flags' because there's no way to retrieve flags from
+ * an OpenFlow switch. We have to assume that they are the same. */
+static bool
+fte_version_equals(const struct fte_version *a, const struct fte_version *b)
+{
+ return (a->cookie == b->cookie
+ && a->idle_timeout == b->idle_timeout
+ && a->hard_timeout == b->hard_timeout
+ && a->n_actions == b->n_actions
+ && !memcmp(a->actions, b->actions,
+ a->n_actions * sizeof *a->actions));
+}
+
+/* Prints 'version' on stdout. Expects the caller to have printed the rule
+ * associated with the version. */
+static void
+fte_version_print(const struct fte_version *version)
+{
+ struct ds s;
+
+ if (version->cookie != htonll(0)) {
+ printf(" cookie=0x%"PRIx64, ntohll(version->cookie));
+ }
+ if (version->idle_timeout != OFP_FLOW_PERMANENT) {
+ printf(" idle_timeout=%"PRIu16, version->idle_timeout);
+ }
+ if (version->hard_timeout != OFP_FLOW_PERMANENT) {
+ printf(" hard_timeout=%"PRIu16, version->hard_timeout);
+ }
+
+ ds_init(&s);
+ ofp_print_actions(&s, (const struct ofp_action_header *) version->actions,
+ version->n_actions * sizeof *version->actions);
+ printf(" %s\n", ds_cstr(&s));
+ ds_destroy(&s);
+}
+
+static struct fte *
+fte_from_cls_rule(const struct cls_rule *cls_rule)
+{
+ return cls_rule ? CONTAINER_OF(cls_rule, struct fte, rule) : NULL;
+}
+
+/* Frees 'fte' and its versions. */
+static void
+fte_free(struct fte *fte)
+{
+ if (fte) {
+ fte_version_free(fte->versions[0]);
+ fte_version_free(fte->versions[1]);
+ free(fte);
+ }
+}
+
+/* Frees all of the FTEs within 'cls'. */
+static void
+fte_free_all(struct classifier *cls)
+{
+ struct cls_cursor cursor;
+ struct fte *fte, *next;
+
+ cls_cursor_init(&cursor, cls, NULL);
+ CLS_CURSOR_FOR_EACH_SAFE (fte, next, rule, &cursor) {
+ classifier_remove(cls, &fte->rule);
+ fte_free(fte);
+ }
+}
+
+/* Searches 'cls' for an FTE matching 'rule', inserting a new one if
+ * necessary. Sets 'version' as the version of that rule with the given
+ * 'index', replacing any existing version, if any.
+ *
+ * Takes ownership of 'version'. */
+static void
+fte_insert(struct classifier *cls, const struct cls_rule *rule,
+ struct fte_version *version, int index)
+{
+ struct fte *old, *fte;
+
+ fte = xzalloc(sizeof *fte);
+ fte->rule = *rule;
+ fte->versions[index] = version;
+
+ old = fte_from_cls_rule(classifier_replace(cls, &fte->rule));
+ if (old) {
+ fte_version_free(old->versions[index]);
+ fte->versions[!index] = old->versions[!index];
+ free(old);
+ }
+}
+
+/* Reads the flows in 'filename' as flow table entries in 'cls' for the version
+ * with the specified 'index'. Returns the minimum flow format required to
+ * represent the flows that were read. */
+static enum nx_flow_format
+read_flows_from_file(const char *filename, struct classifier *cls, int index)
+{
+ enum nx_flow_format min_flow_format;
+ struct ds s;
+ FILE *file;
+
+ file = !strcmp(filename, "-") ? stdin : fopen(filename, "r");
+ if (file == NULL) {
+ ovs_fatal(errno, "%s: open", filename);
+ }
+
+ ds_init(&s);
+ min_flow_format = NXFF_OPENFLOW10;
+ while (!ds_get_preprocessed_line(&s, file)) {
+ struct fte_version *version;
+ enum nx_flow_format min_ff;
+ struct ofpbuf actions;
+ struct flow_mod fm;
+
+ ofpbuf_init(&actions, 64);
+ parse_ofp_str(&fm, &actions, ds_cstr(&s));
+
+ version = xmalloc(sizeof *version);
+ version->cookie = fm.cookie;
+ version->idle_timeout = fm.idle_timeout;
+ version->hard_timeout = fm.hard_timeout;
+ version->flags = fm.flags & (OFPFF_SEND_FLOW_REM | OFPFF_EMERG);
+ version->n_actions = actions.size / sizeof *version->actions;
+ version->actions = ofpbuf_steal_data(&actions);
+
+ min_ff = ofputil_min_flow_format(&fm.cr);
+ min_flow_format = MAX(min_flow_format, min_ff);
+ check_final_format_for_flow_mod(min_flow_format);
+
+ fte_insert(cls, &fm.cr, version, index);
+ }
+ ds_destroy(&s);
+
+ if (file != stdin) {
+ fclose(file);
+ }
+
+ return min_flow_format;
+}
+
+/* Reads the OpenFlow flow table from 'vconn', which has currently active flow
+ * format 'flow_format', and adds them as flow table entries in 'cls' for the
+ * version with the specified 'index'. */
+static void
+read_flows_from_switch(struct vconn *vconn, enum nx_flow_format flow_format,
+ struct classifier *cls, int index)
+{
+ struct flow_stats_request fsr;