+ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs,
+ struct ofpbuf *msg,
+ bool flow_age_extension,
+ struct ofpbuf *ofpacts)
+{
+ enum ofperr error;
+ enum ofpraw raw;
+
+ error = (msg->l2
+ ? ofpraw_decode(&raw, msg->l2)
+ : ofpraw_pull(&raw, msg));
+ if (error) {
+ return error;
+ }
+
+ if (!msg->size) {
+ return EOF;
+ } else if (raw == OFPRAW_OFPST_FLOW_REPLY) {
+ const struct ofp10_flow_stats *ofs;
+ size_t length;
+
+ ofs = ofpbuf_try_pull(msg, sizeof *ofs);
+ if (!ofs) {
+ VLOG_WARN_RL(&bad_ofmsg_rl, "OFPST_FLOW reply has %zu leftover "
+ "bytes at end", msg->size);
+ return EINVAL;
+ }
+
+ length = ntohs(ofs->length);
+ if (length < sizeof *ofs) {
+ VLOG_WARN_RL(&bad_ofmsg_rl, "OFPST_FLOW reply claims invalid "
+ "length %zu", length);
+ return EINVAL;
+ }
+
+ if (ofpacts_pull_openflow10(msg, length - sizeof *ofs, ofpacts)) {
+ return EINVAL;
+ }
+
+ fs->cookie = get_32aligned_be64(&ofs->cookie);
+ ofputil_cls_rule_from_ofp10_match(&ofs->match, ntohs(ofs->priority),
+ &fs->rule);
+ fs->table_id = ofs->table_id;
+ fs->duration_sec = ntohl(ofs->duration_sec);
+ fs->duration_nsec = ntohl(ofs->duration_nsec);
+ fs->idle_timeout = ntohs(ofs->idle_timeout);
+ fs->hard_timeout = ntohs(ofs->hard_timeout);
+ fs->idle_age = -1;
+ fs->hard_age = -1;
+ fs->packet_count = ntohll(get_32aligned_be64(&ofs->packet_count));
+ fs->byte_count = ntohll(get_32aligned_be64(&ofs->byte_count));
+ } else if (raw == OFPRAW_NXST_FLOW_REPLY) {
+ const struct nx_flow_stats *nfs;
+ size_t match_len, actions_len, length;
+
+ nfs = ofpbuf_try_pull(msg, sizeof *nfs);
+ if (!nfs) {
+ VLOG_WARN_RL(&bad_ofmsg_rl, "NXST_FLOW reply has %zu leftover "
+ "bytes at end", msg->size);
+ return EINVAL;
+ }
+
+ length = ntohs(nfs->length);
+ match_len = ntohs(nfs->match_len);
+ if (length < sizeof *nfs + ROUND_UP(match_len, 8)) {
+ VLOG_WARN_RL(&bad_ofmsg_rl, "NXST_FLOW reply with match_len=%zu "
+ "claims invalid length %zu", match_len, length);
+ return EINVAL;
+ }
+ if (nx_pull_match(msg, match_len, ntohs(nfs->priority), &fs->rule,
+ NULL, NULL)) {
+ return EINVAL;
+ }
+
+ actions_len = length - sizeof *nfs - ROUND_UP(match_len, 8);
+ if (ofpacts_pull_openflow10(msg, actions_len, ofpacts)) {
+ return EINVAL;
+ }
+
+ fs->cookie = nfs->cookie;
+ fs->table_id = nfs->table_id;
+ fs->duration_sec = ntohl(nfs->duration_sec);
+ fs->duration_nsec = ntohl(nfs->duration_nsec);
+ fs->idle_timeout = ntohs(nfs->idle_timeout);
+ fs->hard_timeout = ntohs(nfs->hard_timeout);
+ fs->idle_age = -1;
+ fs->hard_age = -1;
+ if (flow_age_extension) {
+ if (nfs->idle_age) {
+ fs->idle_age = ntohs(nfs->idle_age) - 1;
+ }
+ if (nfs->hard_age) {
+ fs->hard_age = ntohs(nfs->hard_age) - 1;
+ }
+ }
+ fs->packet_count = ntohll(nfs->packet_count);
+ fs->byte_count = ntohll(nfs->byte_count);
+ } else {
+ NOT_REACHED();
+ }
+
+ fs->ofpacts = ofpacts->data;
+ fs->ofpacts_len = ofpacts->size;
+
+ return 0;
+}
+
+/* Returns 'count' unchanged except that UINT64_MAX becomes 0.
+ *
+ * We use this in situations where OVS internally uses UINT64_MAX to mean
+ * "value unknown" but OpenFlow 1.0 does not define any unknown value. */
+static uint64_t
+unknown_to_zero(uint64_t count)
+{
+ return count != UINT64_MAX ? count : 0;
+}
+
+/* Appends an OFPST_FLOW or NXST_FLOW reply that contains the data in 'fs' to
+ * those already present in the list of ofpbufs in 'replies'. 'replies' should
+ * have been initialized with ofputil_start_stats_reply(). */
+void
+ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *fs,
+ struct list *replies)
+{
+ struct ofpbuf *reply = ofpbuf_from_list(list_back(replies));
+ size_t start_ofs = reply->size;
+ enum ofpraw raw;
+
+ ofpraw_decode_partial(&raw, reply->data, reply->size);
+ if (raw == OFPRAW_OFPST_FLOW_REPLY) {
+ struct ofp10_flow_stats *ofs;
+
+ ofpbuf_put_uninit(reply, sizeof *ofs);
+ ofpacts_put_openflow10(fs->ofpacts, fs->ofpacts_len, reply);
+
+ ofs = ofpbuf_at_assert(reply, start_ofs, sizeof *ofs);
+ ofs->length = htons(reply->size - start_ofs);
+ ofs->table_id = fs->table_id;
+ ofs->pad = 0;
+ ofputil_cls_rule_to_ofp10_match(&fs->rule, &ofs->match);
+ ofs->duration_sec = htonl(fs->duration_sec);
+ ofs->duration_nsec = htonl(fs->duration_nsec);
+ ofs->priority = htons(fs->rule.priority);
+ ofs->idle_timeout = htons(fs->idle_timeout);
+ ofs->hard_timeout = htons(fs->hard_timeout);
+ memset(ofs->pad2, 0, sizeof ofs->pad2);
+ put_32aligned_be64(&ofs->cookie, fs->cookie);
+ put_32aligned_be64(&ofs->packet_count,
+ htonll(unknown_to_zero(fs->packet_count)));
+ put_32aligned_be64(&ofs->byte_count,
+ htonll(unknown_to_zero(fs->byte_count)));
+ } else if (raw == OFPRAW_NXST_FLOW_REPLY) {
+ struct nx_flow_stats *nfs;
+ int match_len;
+
+ ofpbuf_put_uninit(reply, sizeof *nfs);
+ match_len = nx_put_match(reply, false, &fs->rule, 0, 0);
+ ofpacts_put_openflow10(fs->ofpacts, fs->ofpacts_len, reply);
+
+ nfs = ofpbuf_at_assert(reply, start_ofs, sizeof *nfs);
+ nfs->length = htons(reply->size - start_ofs);
+ nfs->table_id = fs->table_id;
+ nfs->pad = 0;
+ nfs->duration_sec = htonl(fs->duration_sec);
+ nfs->duration_nsec = htonl(fs->duration_nsec);
+ nfs->priority = htons(fs->rule.priority);
+ nfs->idle_timeout = htons(fs->idle_timeout);
+ nfs->hard_timeout = htons(fs->hard_timeout);
+ nfs->idle_age = htons(fs->idle_age < 0 ? 0
+ : fs->idle_age < UINT16_MAX ? fs->idle_age + 1
+ : UINT16_MAX);
+ nfs->hard_age = htons(fs->hard_age < 0 ? 0
+ : fs->hard_age < UINT16_MAX ? fs->hard_age + 1
+ : UINT16_MAX);
+ nfs->match_len = htons(match_len);
+ nfs->cookie = fs->cookie;
+ nfs->packet_count = htonll(fs->packet_count);
+ nfs->byte_count = htonll(fs->byte_count);
+ } else {
+ NOT_REACHED();
+ }
+
+ ofpmp_postappend(replies, start_ofs);
+}
+
+/* Converts abstract ofputil_aggregate_stats 'stats' into an OFPST_AGGREGATE or
+ * NXST_AGGREGATE reply matching 'request', and returns the message. */
+struct ofpbuf *
+ofputil_encode_aggregate_stats_reply(
+ const struct ofputil_aggregate_stats *stats,
+ const struct ofp_header *request)
+{
+ struct ofp_aggregate_stats_reply *asr;
+ uint64_t packet_count;
+ uint64_t byte_count;
+ struct ofpbuf *msg;
+ enum ofpraw raw;
+
+ ofpraw_decode(&raw, request);
+ if (raw == OFPRAW_OFPST_AGGREGATE_REQUEST) {
+ packet_count = unknown_to_zero(stats->packet_count);
+ byte_count = unknown_to_zero(stats->byte_count);
+ } else {
+ packet_count = stats->packet_count;
+ byte_count = stats->byte_count;
+ }
+
+ msg = ofpraw_alloc_stats_reply(request, 0);
+ asr = ofpbuf_put_zeros(msg, sizeof *asr);
+ put_32aligned_be64(&asr->packet_count, htonll(packet_count));
+ put_32aligned_be64(&asr->byte_count, htonll(byte_count));
+ asr->flow_count = htonl(stats->flow_count);
+
+ return msg;
+}
+
+enum ofperr
+ofputil_decode_aggregate_stats_reply(struct ofputil_aggregate_stats *stats,
+ const struct ofp_header *reply)
+{
+ struct ofp_aggregate_stats_reply *asr;
+ struct ofpbuf msg;
+
+ ofpbuf_use_const(&msg, reply, ntohs(reply->length));
+ ofpraw_pull_assert(&msg);
+
+ asr = msg.l3;
+ stats->packet_count = ntohll(get_32aligned_be64(&asr->packet_count));
+ stats->byte_count = ntohll(get_32aligned_be64(&asr->byte_count));
+ stats->flow_count = ntohl(asr->flow_count);
+
+ return 0;
+}
+
+/* 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. */
+enum ofperr